home *** CD-ROM | disk | FTP | other *** search
/ Freelog 115 / FreelogNo115-MaiJuin2013.iso / Internet / AvantBrowser / asetup.exe / _data / webkit / resources.pak / Unnamed File 000298.txt < prev    next >
Text File  |  2013-04-03  |  513KB  |  15,518 lines

  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. //
  5. // This file exists to aggregate all of the javascript used by the
  6. // settings page into a single file which will be flattened and served
  7. // as a single resource.
  8. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9. // Use of this source code is governed by a BSD-style license that can be
  10. // found in the LICENSE file.
  11.  
  12. cr.define('options', function() {
  13.  
  14.   /////////////////////////////////////////////////////////////////////////////
  15.   // Preferences class:
  16.  
  17.   /**
  18.    * Preferences class manages access to Chrome profile preferences.
  19.    * @constructor
  20.    */
  21.   function Preferences() {
  22.     // Map of registered preferences.
  23.     this.registeredPreferences_ = {};
  24.   }
  25.  
  26.   cr.addSingletonGetter(Preferences);
  27.  
  28.   /**
  29.    * Sets a Boolean preference and signals its new value.
  30.    * @param {string} name Preference name.
  31.    * @param {boolean} value New preference value.
  32.    * @param {boolean} commit Whether to commit the change to Chrome.
  33.    * @param {string} metric User metrics identifier.
  34.    */
  35.   Preferences.setBooleanPref = function(name, value, commit, metric) {
  36.     if (!commit) {
  37.       Preferences.getInstance().setPrefNoCommit_(name, 'bool', Boolean(value));
  38.       return;
  39.     }
  40.  
  41.     var argumentList = [name, Boolean(value)];
  42.     if (metric != undefined) argumentList.push(metric);
  43.     chrome.send('setBooleanPref', argumentList);
  44.   };
  45.  
  46.   /**
  47.    * Sets an integer preference and signals its new value.
  48.    * @param {string} name Preference name.
  49.    * @param {number} value New preference value.
  50.    * @param {boolean} commit Whether to commit the change to Chrome.
  51.    * @param {string} metric User metrics identifier.
  52.    */
  53.   Preferences.setIntegerPref = function(name, value, commit, metric) {
  54.     if (!commit) {
  55.       Preferences.getInstance().setPrefNoCommit_(name, 'int', Number(value));
  56.       return;
  57.     }
  58.  
  59.     var argumentList = [name, Number(value)];
  60.     if (metric != undefined) argumentList.push(metric);
  61.     chrome.send('setIntegerPref', argumentList);
  62.   };
  63.  
  64.   /**
  65.    * Sets a double-valued preference and signals its new value.
  66.    * @param {string} name Preference name.
  67.    * @param {number} value New preference value.
  68.    * @param {boolean} commit Whether to commit the change to Chrome.
  69.    * @param {string} metric User metrics identifier.
  70.    */
  71.   Preferences.setDoublePref = function(name, value, commit, metric) {
  72.     if (!commit) {
  73.       Preferences.getInstance().setPrefNoCommit_(name, 'double', Number(value));
  74.       return;
  75.     }
  76.  
  77.     var argumentList = [name, Number(value)];
  78.     if (metric != undefined) argumentList.push(metric);
  79.     chrome.send('setDoublePref', argumentList);
  80.   };
  81.  
  82.   /**
  83.    * Sets a string preference and signals its new value.
  84.    * @param {string} name Preference name.
  85.    * @param {string} value New preference value.
  86.    * @param {boolean} commit Whether to commit the change to Chrome.
  87.    * @param {string} metric User metrics identifier.
  88.    */
  89.   Preferences.setStringPref = function(name, value, commit, metric) {
  90.     if (!commit) {
  91.       Preferences.getInstance().setPrefNoCommit_(name, 'string', String(value));
  92.       return;
  93.     }
  94.  
  95.     var argumentList = [name, String(value)];
  96.     if (metric != undefined) argumentList.push(metric);
  97.     chrome.send('setStringPref', argumentList);
  98.   };
  99.  
  100.   /**
  101.    * Sets a string preference that represents a URL and signals its new value.
  102.    * The value will be fixed to be a valid URL when it gets committed to Chrome.
  103.    * @param {string} name Preference name.
  104.    * @param {string} value New preference value.
  105.    * @param {boolean} commit Whether to commit the change to Chrome.
  106.    * @param {string} metric User metrics identifier.
  107.    */
  108.   Preferences.setURLPref = function(name, value, commit, metric) {
  109.     if (!commit) {
  110.       Preferences.getInstance().setPrefNoCommit_(name, 'url', String(value));
  111.       return;
  112.     }
  113.  
  114.     var argumentList = [name, String(value)];
  115.     if (metric != undefined) argumentList.push(metric);
  116.     chrome.send('setURLPref', argumentList);
  117.   };
  118.  
  119.   /**
  120.    * Sets a JSON list preference and signals its new value.
  121.    * @param {string} name Preference name.
  122.    * @param {Array} value New preference value.
  123.    * @param {boolean} commit Whether to commit the change to Chrome.
  124.    * @param {string} metric User metrics identifier.
  125.    */
  126.   Preferences.setListPref = function(name, value, commit, metric) {
  127.     if (!commit) {
  128.       Preferences.getInstance().setPrefNoCommit_(name, 'list', value);
  129.       return;
  130.     }
  131.  
  132.     var argumentList = [name, JSON.stringify(value)];
  133.     if (metric != undefined) argumentList.push(metric);
  134.     chrome.send('setListPref', argumentList);
  135.   };
  136.  
  137.   /**
  138.    * Clears the user setting for a preference and signals its new effective
  139.    * value.
  140.    * @param {string} name Preference name.
  141.    * @param {boolean} commit Whether to commit the change to Chrome.
  142.    * @param {string} metric User metrics identifier.
  143.    */
  144.   Preferences.clearPref = function(name, commit, metric) {
  145.     if (!commit) {
  146.       Preferences.getInstance().clearPrefNoCommit_(name);
  147.       return;
  148.     }
  149.  
  150.     var argumentList = [name];
  151.     if (metric != undefined) argumentList.push(metric);
  152.     chrome.send('clearPref', argumentList);
  153.   };
  154.  
  155.   Preferences.prototype = {
  156.     __proto__: cr.EventTarget.prototype,
  157.  
  158.     /**
  159.      * Adds an event listener to the target.
  160.      * @param {string} type The name of the event.
  161.      * @param {!Function|{handleEvent:Function}} handler The handler for the
  162.      *     event. This is called when the event is dispatched.
  163.      */
  164.     addEventListener: function(type, handler) {
  165.       cr.EventTarget.prototype.addEventListener.call(this, type, handler);
  166.       if (!(type in this.registeredPreferences_))
  167.         this.registeredPreferences_[type] = {};
  168.     },
  169.  
  170.     /**
  171.      * Initializes preference reading and change notifications.
  172.      */
  173.     initialize: function() {
  174.       var params1 = ['Preferences.prefsFetchedCallback'];
  175.       var params2 = ['Preferences.prefsChangedCallback'];
  176.       for (var prefName in this.registeredPreferences_) {
  177.         params1.push(prefName);
  178.         params2.push(prefName);
  179.       }
  180.       chrome.send('fetchPrefs', params1);
  181.       chrome.send('observePrefs', params2);
  182.     },
  183.  
  184.     /**
  185.      * Helper function for flattening of dictionary passed via fetchPrefs
  186.      * callback.
  187.      * @param {string} prefix Preference name prefix.
  188.      * @param {object} dict Map with preference values.
  189.      * @private
  190.      */
  191.     flattenMapAndDispatchEvent_: function(prefix, dict) {
  192.       for (var prefName in dict) {
  193.         if (typeof dict[prefName] == 'object' &&
  194.             !this.registeredPreferences_[prefix + prefName]) {
  195.           this.flattenMapAndDispatchEvent_(prefix + prefName + '.',
  196.               dict[prefName]);
  197.         } else {
  198.           var event = new cr.Event(prefix + prefName);
  199.           this.registeredPreferences_[prefix + prefName].orig = dict[prefName];
  200.           event.value = dict[prefName];
  201.           this.dispatchEvent(event);
  202.         }
  203.       }
  204.     },
  205.  
  206.     /**
  207.      * Sets a preference and signals its new value. The change is propagated
  208.      * throughout the UI code but is not committed to Chrome yet. The new value
  209.      * and its data type are stored so that commitPref() can later be used to
  210.      * invoke the appropriate set*Pref() method and actually commit the change.
  211.      * @param {string} name Preference name.
  212.      * @param {string} type Preference data type.
  213.      * @param {*} value New preference value.
  214.      * @private
  215.      */
  216.     setPrefNoCommit_: function(name, type, value) {
  217.       var pref = this.registeredPreferences_[name];
  218.       pref.action = 'set';
  219.       pref.type = type;
  220.       pref.value = value;
  221.  
  222.       var event = new cr.Event(name);
  223.       // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
  224.       event.value = {
  225.         value: value,
  226.         recommendedValue: pref.orig.recommendedValue,
  227.         disabled: pref.orig.disabled,
  228.         uncommitted: true,
  229.       };
  230.       this.dispatchEvent(event);
  231.     },
  232.  
  233.     /**
  234.      * Clears a preference and signals its new value. The change is propagated
  235.      * throughout the UI code but is not committed to Chrome yet.
  236.      * @param {string} name Preference name.
  237.      * @private
  238.      */
  239.     clearPrefNoCommit_: function(name) {
  240.       var pref = this.registeredPreferences_[name];
  241.       pref.action = 'clear';
  242.       delete pref.type;
  243.       delete pref.value;
  244.  
  245.       var event = new cr.Event(name);
  246.       // Decorate pref value as CoreOptionsHandler::CreateValueForPref() does.
  247.       event.value = {
  248.         value: pref.orig.recommendedValue,
  249.         controlledBy: 'recommended',
  250.         recommendedValue: pref.orig.recommendedValue,
  251.         disabled: pref.orig.disabled,
  252.         uncommitted: true,
  253.       };
  254.       this.dispatchEvent(event);
  255.     },
  256.  
  257.     /**
  258.      * Commits a preference change to Chrome and signals the new preference
  259.      * value. Does nothing if there is no uncommitted change.
  260.      * @param {string} name Preference name.
  261.      * @param {string} metric User metrics identifier.
  262.      */
  263.     commitPref: function(name, metric) {
  264.       var pref = this.registeredPreferences_[name];
  265.       switch (pref.action) {
  266.         case 'set':
  267.           switch (pref.type) {
  268.             case 'bool':
  269.               Preferences.setBooleanPref(name, pref.value, true, metric);
  270.               break;
  271.             case 'int':
  272.               Preferences.setIntegerPref(name, pref.value, true, metric);
  273.               break;
  274.             case 'double':
  275.               Preferences.setDoublePref(name, pref.value, true, metric);
  276.               break;
  277.             case 'string':
  278.               Preferences.setStringPref(name, pref.value, true, metric);
  279.               break;
  280.             case 'url':
  281.               Preferences.setURLPref(name, pref.value, true, metric);
  282.               break;
  283.             case 'list':
  284.               Preferences.setListPref(name, pref.value, true, metric);
  285.               break;
  286.           }
  287.           break;
  288.         case 'clear':
  289.           Preferences.clearPref(name, true, metric);
  290.           break;
  291.       }
  292.       delete pref.action;
  293.       delete pref.type;
  294.       delete pref.value;
  295.     },
  296.  
  297.     /**
  298.      * Rolls back a preference change and signals the original preference value.
  299.      * Does nothing if there is no uncommitted change.
  300.      * @param {string} name Preference name.
  301.      */
  302.     rollbackPref: function(name) {
  303.       var pref = this.registeredPreferences_[name];
  304.       if (!pref.action)
  305.         return;
  306.  
  307.       delete pref.action;
  308.       delete pref.type;
  309.       delete pref.value;
  310.  
  311.       var event = new cr.Event(name);
  312.       event.value = pref.orig;
  313.       event.value.uncommitted = true;
  314.       this.dispatchEvent(event);
  315.     }
  316.   };
  317.  
  318.   /**
  319.    * Callback for fetchPrefs method.
  320.    * @param {object} dict Map of fetched property values.
  321.    */
  322.   Preferences.prefsFetchedCallback = function(dict) {
  323.     Preferences.getInstance().flattenMapAndDispatchEvent_('', dict);
  324.   };
  325.  
  326.   /**
  327.    * Callback for observePrefs method.
  328.    * @param {array} notification An array defining changed preference values.
  329.    * notification[0] contains name of the change preference while its new value
  330.    * is stored in notification[1].
  331.    */
  332.   Preferences.prefsChangedCallback = function(notification) {
  333.     var event = new cr.Event(notification[0]);
  334.     event.value = notification[1];
  335.     prefs = Preferences.getInstance();
  336.     prefs.registeredPreferences_[notification[0]] = {orig: notification[1]};
  337.     prefs.dispatchEvent(event);
  338.   };
  339.  
  340.   // Export
  341.   return {
  342.     Preferences: Preferences
  343.   };
  344.  
  345. });
  346.  
  347. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  348. // Use of this source code is governed by a BSD-style license that can be
  349. // found in the LICENSE file.
  350.  
  351. cr.define('options', function() {
  352.   var BubbleBase = cr.ui.BubbleBase;
  353.  
  354.   var OptionsBubble = cr.ui.define('div');
  355.  
  356.   OptionsBubble.prototype = {
  357.     // Set up the prototype chain.
  358.     __proto__: BubbleBase.prototype,
  359.  
  360.     /**
  361.      * Initialization function for the cr.ui framework.
  362.      */
  363.     decorate: function() {
  364.       BubbleBase.prototype.decorate.call(this);
  365.       this.classList.add('options-bubble');
  366.     },
  367.  
  368.     /**
  369.      * Set the DOM sibling node, i.e. the node as whose sibling the bubble
  370.      * should join the DOM to ensure that focusable elements inside the bubble
  371.      * follow the target element in the document's tab order. Only available
  372.      * when the bubble is not being shown.
  373.      * @param {HTMLElement} node The new DOM sibling node.
  374.      */
  375.     set domSibling(node) {
  376.       if (!this.hidden)
  377.         return;
  378.  
  379.       this.domSibling_ = node;
  380.     },
  381.  
  382.     /**
  383.      * Show the bubble.
  384.      */
  385.     show: function() {
  386.       if (!this.hidden)
  387.         return;
  388.  
  389.       BubbleBase.prototype.show.call(this);
  390.       this.domSibling_.showingBubble = true;
  391.  
  392.       var doc = this.ownerDocument;
  393.       this.eventTracker_.add(doc, 'mousewheel', this, true);
  394.       this.eventTracker_.add(doc, 'scroll', this, true);
  395.       this.eventTracker_.add(doc, 'elementFocused', this, true);
  396.       this.eventTracker_.add(window, 'resize', this);
  397.     },
  398.  
  399.     /**
  400.      * Hide the bubble.
  401.      */
  402.     hide: function() {
  403.       BubbleBase.prototype.hide.call(this);
  404.       this.domSibling_.showingBubble = false;
  405.     },
  406.  
  407.     /**
  408.      * Handle events, closing the bubble when the user clicks or moves the focus
  409.      * outside the bubble and its target element, scrolls the underlying
  410.      * document or resizes the window.
  411.      * @param {Event} event The event.
  412.      */
  413.     handleEvent: function(event) {
  414.       BubbleBase.prototype.handleEvent.call(this, event);
  415.  
  416.       switch (event.type) {
  417.         // Close the bubble when the user clicks outside it, except if it is a
  418.         // left-click on the bubble's target element (allowing the target to
  419.         // handle the event and close the bubble itself).
  420.         case 'mousedown':
  421.           if (event.button == 0 && this.anchorNode_.contains(event.target))
  422.             break;
  423.         // Close the bubble when the underlying document is scrolled.
  424.         case 'mousewheel':
  425.         case 'scroll':
  426.           if (this.contains(event.target))
  427.             break;
  428.         // Close the bubble when the window is resized.
  429.         case 'resize':
  430.           this.hide();
  431.           break;
  432.         // Close the bubble when the focus moves to an element that is not the
  433.         // bubble target and is not inside the bubble.
  434.         case 'elementFocused':
  435.           if (!this.anchorNode_.contains(event.target) &&
  436.               !this.contains(event.target)) {
  437.             this.hide();
  438.           }
  439.           break;
  440.       }
  441.     },
  442.  
  443.     /**
  444.      * Attach the bubble to the document's DOM, making it a sibling of the
  445.      * |domSibling_| so that focusable elements inside the bubble follow the
  446.      * target element in the document's tab order.
  447.      * @private
  448.      */
  449.     attachToDOM_: function() {
  450.       var parent = this.domSibling_.parentNode;
  451.       parent.insertBefore(this, this.domSibling_.nextSibling);
  452.     },
  453.   };
  454.  
  455.   return {
  456.     OptionsBubble: OptionsBubble
  457.   };
  458. });
  459.  
  460. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  461. // Use of this source code is governed by a BSD-style license that can be
  462. // found in the LICENSE file.
  463.  
  464. cr.define('options', function() {
  465.   var Preferences = options.Preferences;
  466.  
  467.   /**
  468.    * A controlled setting indicator that can be placed on a setting as an
  469.    * indicator that the value is controlled by some external entity such as
  470.    * policy or an extension.
  471.    * @constructor
  472.    * @extends {HTMLSpanElement}
  473.    */
  474.   var ControlledSettingIndicator = cr.ui.define('span');
  475.  
  476.   ControlledSettingIndicator.prototype = {
  477.     __proto__: HTMLSpanElement.prototype,
  478.  
  479.     /**
  480.      * Decorates the base element to show the proper icon.
  481.      */
  482.     decorate: function() {
  483.       var self = this;
  484.  
  485.       // If there is a pref, track its controlledBy and recommendedValue
  486.       // properties in order to be able to bring up the correct bubble.
  487.       if (this.pref) {
  488.         Preferences.getInstance().addEventListener(
  489.             this.pref, this.handlePrefChange.bind(this));
  490.         this.resetHandler = this.clearAssociatedPref_;
  491.       }
  492.  
  493.       this.className = 'controlled-setting-indicator';
  494.       this.location = cr.ui.ArrowLocation.TOP_END;
  495.       this.image = document.createElement('div');
  496.       this.image.tabIndex = 0;
  497.       this.image.setAttribute('role', 'button');
  498.       this.image.addEventListener('click', this);
  499.       this.image.addEventListener('keydown', this);
  500.       this.image.addEventListener('mousedown', this);
  501.       this.appendChild(this.image);
  502.     },
  503.  
  504.     /**
  505.      * The given handler will be called when the user clicks on the 'reset to
  506.      * recommended value' link shown in the indicator bubble. The |this| object
  507.      * will be the indicator itself.
  508.      * @param {function()} handler The handler to be called.
  509.      */
  510.     set resetHandler(handler) {
  511.       this.resetHandler_ = handler;
  512.     },
  513.  
  514.     /**
  515.      * Whether the indicator is currently showing a bubble.
  516.      * @type {boolean}
  517.      */
  518.     get showingBubble() {
  519.       return this.image.classList.contains('showing-bubble');
  520.     },
  521.     set showingBubble(showing) {
  522.       this.image.classList.toggle('showing-bubble', showing);
  523.     },
  524.  
  525.     /**
  526.      * Clears the preference associated with this indicator.
  527.      * @private
  528.      */
  529.     clearAssociatedPref_: function() {
  530.       Preferences.clearPref(this.pref, !this.dialogPref);
  531.     },
  532.  
  533.     /* Handle changes to the associated pref by hiding any currently visible
  534.      * bubble and updating the controlledBy property.
  535.      * @param {Event} event Pref change event.
  536.      */
  537.     handlePrefChange: function(event) {
  538.       OptionsPage.hideBubble();
  539.       if (event.value.controlledBy) {
  540.         this.controlledBy =
  541.             !this.value || String(event.value.value) == this.value ?
  542.             event.value.controlledBy : null;
  543.       } else if (event.value.recommendedValue != undefined) {
  544.         this.controlledBy =
  545.             !this.value || String(event.value.recommendedValue) == this.value ?
  546.             'hasRecommendation' : null;
  547.       } else {
  548.         this.controlledBy = null;
  549.       }
  550.     },
  551.  
  552.     /**
  553.      * Handle mouse and keyboard events, allowing the user to open and close a
  554.      * bubble with further information.
  555.      * @param {Event} event Mouse or keyboard event.
  556.      */
  557.     handleEvent: function(event) {
  558.       switch (event.type) {
  559.         // Toggle the bubble on left click. Let any other clicks propagate.
  560.         case 'click':
  561.           if (event.button != 0)
  562.             return;
  563.           break;
  564.         // Toggle the bubble when <Return> or <Space> is pressed. Let any other
  565.         // key presses propagate.
  566.         case 'keydown':
  567.           switch (event.keyCode) {
  568.             case 13:  // Return.
  569.             case 32:  // Space.
  570.               break;
  571.             default:
  572.               return;
  573.           }
  574.           break;
  575.         // Blur focus when a mouse button is pressed, matching the behavior of
  576.         // other Web UI elements.
  577.         case 'mousedown':
  578.           if (document.activeElement)
  579.             document.activeElement.blur();
  580.           event.preventDefault();
  581.           return;
  582.       }
  583.       this.toggleBubble_();
  584.       event.preventDefault();
  585.       event.stopPropagation();
  586.     },
  587.  
  588.     /**
  589.      * Open or close a bubble with further information about the pref.
  590.      * @private
  591.      */
  592.     toggleBubble_: function() {
  593.       if (this.showingBubble) {
  594.         OptionsPage.hideBubble();
  595.       } else {
  596.         var self = this;
  597.  
  598.         // Construct the bubble text.
  599.         if (this.hasAttribute('plural')) {
  600.           var defaultStrings = {
  601.             'policy': loadTimeData.getString('controlledSettingsPolicy'),
  602.             'extension': loadTimeData.getString('controlledSettingsExtension'),
  603.           };
  604.         } else {
  605.           var defaultStrings = {
  606.             'policy': loadTimeData.getString('controlledSettingPolicy'),
  607.             'extension': loadTimeData.getString('controlledSettingExtension'),
  608.             'recommended':
  609.                 loadTimeData.getString('controlledSettingRecommended'),
  610.             'hasRecommendation':
  611.                 loadTimeData.getString('controlledSettingHasRecommendation'),
  612.           };
  613.         }
  614.  
  615.         // No controller, no bubble.
  616.         if (!this.controlledBy || !(this.controlledBy in defaultStrings))
  617.           return;
  618.  
  619.         var text = defaultStrings[this.controlledBy];
  620.  
  621.         // Apply text overrides.
  622.         if (this.hasAttribute('text' + this.controlledBy))
  623.           text = this.getAttribute('text' + this.controlledBy);
  624.  
  625.         // Create the DOM tree.
  626.         var content = document.createElement('div');
  627.         content.className = 'controlled-setting-bubble-content';
  628.         content.setAttribute('controlled-by', this.controlledBy);
  629.         content.textContent = text;
  630.  
  631.         if (this.controlledBy == 'hasRecommendation' && this.resetHandler_ &&
  632.             !this.readOnly) {
  633.           var container = document.createElement('div');
  634.           var action = document.createElement('button');
  635.           action.classList.add('link-button');
  636.           action.classList.add('controlled-setting-bubble-action');
  637.           action.textContent =
  638.               loadTimeData.getString('controlledSettingFollowRecommendation');
  639.           action.addEventListener('click', function(event) {
  640.             self.resetHandler_();
  641.           });
  642.           container.appendChild(action);
  643.           content.appendChild(container);
  644.         }
  645.  
  646.         OptionsPage.showBubble(content, this.image, this, this.location);
  647.       }
  648.     },
  649.   };
  650.  
  651.   /**
  652.    * The name of the associated preference.
  653.    * @type {string}
  654.    */
  655.   cr.defineProperty(ControlledSettingIndicator, 'pref', cr.PropertyKind.ATTR);
  656.  
  657.   /**
  658.    * Whether this indicator is part of a dialog. If so, changes made to the
  659.    * associated preference take effect in the settings UI immediately but are
  660.    * only actually committed when the user confirms the dialog. If the user
  661.    * cancels the dialog instead, the changes are rolled back in the settings UI
  662.    * and never committed.
  663.    * @type {boolean}
  664.    */
  665.   cr.defineProperty(ControlledSettingIndicator, 'dialogPref',
  666.                     cr.PropertyKind.BOOL_ATTR);
  667.  
  668.   /**
  669.    * The value of the associated preference that the indicator represents. If
  670.    * this is not set, the indicator will be visible whenever any value is
  671.    * enforced or recommended. If it is set, the indicator will be visible only
  672.    * when the enforced or recommended value matches the value it represents.
  673.    * This allows multiple indicators to be created for a set of radio buttons,
  674.    * ensuring that only one of them is visible at a time.
  675.    */
  676.   cr.defineProperty(ControlledSettingIndicator, 'value',
  677.                     cr.PropertyKind.ATTR);
  678.  
  679.   /**
  680.    * The status of the associated preference:
  681.    * - 'policy':            A specific value is enfoced by policy.
  682.    * - 'extension':         A specific value is enforced by an extension.
  683.    * - 'recommended':       A value is recommended by policy. The user could
  684.    *                        override this recommendation but has not done so.
  685.    * - 'hasRecommendation': A value is recommended by policy. The user has
  686.    *                        overridden this recommendation.
  687.    * - unset:               The value is controlled by the user alone.
  688.    * @type {string}
  689.    */
  690.   cr.defineProperty(ControlledSettingIndicator, 'controlledBy',
  691.                     cr.PropertyKind.ATTR);
  692.  
  693.   // Export.
  694.   return {
  695.     ControlledSettingIndicator: ControlledSettingIndicator
  696.   };
  697. });
  698.  
  699. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  700. // Use of this source code is governed by a BSD-style license that can be
  701. // found in the LICENSE file.
  702.  
  703. cr.define('options', function() {
  704.   /** @const */ var List = cr.ui.List;
  705.   /** @const */ var ListItem = cr.ui.ListItem;
  706.  
  707.   /**
  708.    * Creates a deletable list item, which has a button that will trigger a call
  709.    * to deleteItemAtIndex(index) in the list.
  710.    */
  711.   var DeletableItem = cr.ui.define('li');
  712.  
  713.   DeletableItem.prototype = {
  714.     __proto__: ListItem.prototype,
  715.  
  716.     /**
  717.      * The element subclasses should populate with content.
  718.      * @type {HTMLElement}
  719.      * @private
  720.      */
  721.     contentElement_: null,
  722.  
  723.     /**
  724.      * The close button element.
  725.      * @type {HTMLElement}
  726.      * @private
  727.      */
  728.     closeButtonElement_: null,
  729.  
  730.     /**
  731.      * Whether or not this item can be deleted.
  732.      * @type {boolean}
  733.      * @private
  734.      */
  735.     deletable_: true,
  736.  
  737.     /** @override */
  738.     decorate: function() {
  739.       ListItem.prototype.decorate.call(this);
  740.  
  741.       this.classList.add('deletable-item');
  742.  
  743.       this.contentElement_ = this.ownerDocument.createElement('div');
  744.       this.appendChild(this.contentElement_);
  745.  
  746.       this.closeButtonElement_ = this.ownerDocument.createElement('button');
  747.       this.closeButtonElement_.className =
  748.           'raw-button row-delete-button custom-appearance';
  749.       this.closeButtonElement_.addEventListener('mousedown',
  750.                                                 this.handleMouseDownUpOnClose_);
  751.       this.closeButtonElement_.addEventListener('mouseup',
  752.                                                 this.handleMouseDownUpOnClose_);
  753.       this.closeButtonElement_.addEventListener('focus',
  754.                                                 this.handleFocus_.bind(this));
  755.       this.appendChild(this.closeButtonElement_);
  756.     },
  757.  
  758.     /**
  759.      * Returns the element subclasses should add content to.
  760.      * @return {HTMLElement} The element subclasses should popuplate.
  761.      */
  762.     get contentElement() {
  763.       return this.contentElement_;
  764.     },
  765.  
  766.     /**
  767.      * Returns the close button element.
  768.      * @return {HTMLElement} The close |<button>| element.
  769.      */
  770.     get closeButtonElement() {
  771.       return this.closeButtonElement_;
  772.     },
  773.  
  774.     /* Gets/sets the deletable property. An item that is not deletable doesn't
  775.      * show the delete button (although space is still reserved for it).
  776.      */
  777.     get deletable() {
  778.       return this.deletable_;
  779.     },
  780.     set deletable(value) {
  781.       this.deletable_ = value;
  782.       this.closeButtonElement_.disabled = !value;
  783.     },
  784.  
  785.     /**
  786.      * Called when a focusable child element receives focus. Selects this item
  787.      * in the list selection model.
  788.      * @private
  789.      */
  790.     handleFocus_: function() {
  791.       var list = this.parentNode;
  792.       var index = list.getIndexOfListItem(this);
  793.       list.selectionModel.selectedIndex = index;
  794.       list.selectionModel.anchorIndex = index;
  795.     },
  796.  
  797.     /**
  798.      * Don't let the list have a crack at the event. We don't want clicking the
  799.      * close button to change the selection of the list.
  800.      * @param {Event} e The mouse down/up event object.
  801.      * @private
  802.      */
  803.     handleMouseDownUpOnClose_: function(e) {
  804.       if (!e.target.disabled)
  805.         e.stopPropagation();
  806.     },
  807.   };
  808.  
  809.   var DeletableItemList = cr.ui.define('list');
  810.  
  811.   DeletableItemList.prototype = {
  812.     __proto__: List.prototype,
  813.  
  814.     /** @override */
  815.     decorate: function() {
  816.       List.prototype.decorate.call(this);
  817.       this.addEventListener('click', this.handleClick_);
  818.       this.addEventListener('keydown', this.handleKeyDown_);
  819.     },
  820.  
  821.     /**
  822.      * Callback for onclick events.
  823.      * @param {Event} e The click event object.
  824.      * @private
  825.      */
  826.     handleClick_: function(e) {
  827.       if (this.disabled)
  828.         return;
  829.  
  830.       var target = e.target;
  831.       if (target.classList.contains('row-delete-button')) {
  832.         var listItem = this.getListItemAncestor(target);
  833.         var selected = this.selectionModel.selectedIndexes;
  834.  
  835.         // Check if the list item that contains the close button being clicked
  836.         // is not in the list of selected items. Only delete this item in that
  837.         // case.
  838.         var idx = this.getIndexOfListItem(listItem);
  839.         if (selected.indexOf(idx) == -1) {
  840.           this.deleteItemAtIndex(idx);
  841.         } else {
  842.           this.deleteSelectedItems_();
  843.         }
  844.       }
  845.     },
  846.  
  847.     /**
  848.      * Callback for keydown events.
  849.      * @param {Event} e The keydown event object.
  850.      * @private
  851.      */
  852.     handleKeyDown_: function(e) {
  853.       // Map delete (and backspace on Mac) to item deletion (unless focus is
  854.       // in an input field, where it's intended for text editing).
  855.       if ((e.keyCode == 46 || (e.keyCode == 8 && cr.isMac)) &&
  856.           e.target.tagName != 'INPUT') {
  857.         this.deleteSelectedItems_();
  858.         // Prevent the browser from going back.
  859.         e.preventDefault();
  860.       }
  861.     },
  862.  
  863.     /**
  864.      * Deletes all the currently selected items that are deletable.
  865.      * @private
  866.      */
  867.     deleteSelectedItems_: function() {
  868.       var selected = this.selectionModel.selectedIndexes;
  869.       // Reverse through the list of selected indexes to maintain the
  870.       // correct index values after deletion.
  871.       for (var j = selected.length - 1; j >= 0; j--) {
  872.         var index = selected[j];
  873.         if (this.getListItemByIndex(index).deletable)
  874.           this.deleteItemAtIndex(index);
  875.       }
  876.     },
  877.  
  878.     /**
  879.      * Called when an item should be deleted; subclasses are responsible for
  880.      * implementing.
  881.      * @param {number} index The index of the item that is being deleted.
  882.      */
  883.     deleteItemAtIndex: function(index) {
  884.     },
  885.   };
  886.  
  887.   return {
  888.     DeletableItemList: DeletableItemList,
  889.     DeletableItem: DeletableItem,
  890.   };
  891. });
  892.  
  893. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  894. // Use of this source code is governed by a BSD-style license that can be
  895. // found in the LICENSE file.
  896.  
  897. cr.define('options', function() {
  898.   var EditableTextField = cr.ui.define('div');
  899.  
  900.   /**
  901.    * Decorates an element as an editable text field.
  902.    * @param {!HTMLElement} el The element to decorate.
  903.    */
  904.   EditableTextField.decorate = function(el) {
  905.     el.__proto__ = EditableTextField.prototype;
  906.     el.decorate();
  907.   };
  908.  
  909.   EditableTextField.prototype = {
  910.     __proto__: HTMLDivElement.prototype,
  911.  
  912.     /**
  913.      * The actual input element in this field.
  914.      * @type {?HTMLElement}
  915.      * @private
  916.      */
  917.     editField_: null,
  918.  
  919.     /**
  920.      * The static text displayed when this field isn't editable.
  921.      * @type {?HTMLElement}
  922.      * @private
  923.      */
  924.     staticText_: null,
  925.  
  926.     /**
  927.      * The data model for this field.
  928.      * @type {?Object}
  929.      * @private
  930.      */
  931.     model_: null,
  932.  
  933.     /**
  934.      * Whether or not the current edit should be considered canceled, rather
  935.      * than committed, when editing ends.
  936.      * @type {boolean}
  937.      * @private
  938.      */
  939.     editCanceled_: true,
  940.  
  941.     /** @override */
  942.     decorate: function() {
  943.       this.classList.add('editable-text-field');
  944.  
  945.       this.createEditableTextCell();
  946.  
  947.       if (this.hasAttribute('i18n-placeholder-text')) {
  948.         var identifier = this.getAttribute('i18n-placeholder-text');
  949.         var localizedText = loadTimeData.getString(identifier);
  950.         if (localizedText)
  951.           this.setAttribute('placeholder-text', localizedText);
  952.       }
  953.  
  954.       this.addEventListener('keydown', this.handleKeyDown_);
  955.       this.editField_.addEventListener('focus', this.handleFocus_.bind(this));
  956.       this.editField_.addEventListener('blur', this.handleBlur_.bind(this));
  957.       this.checkForEmpty_();
  958.     },
  959.  
  960.     /**
  961.      * Indicates that this field has no value in the model, and the placeholder
  962.      * text (if any) should be shown.
  963.      * @type {boolean}
  964.      */
  965.     get empty() {
  966.       return this.hasAttribute('empty');
  967.     },
  968.  
  969.     /**
  970.      * The placeholder text to be used when the model or its value is empty.
  971.      * @type {string}
  972.      */
  973.     get placeholderText() {
  974.       return this.getAttribute('placeholder-text');
  975.     },
  976.     set placeholderText(text) {
  977.       if (text)
  978.         this.setAttribute('placeholder-text', text);
  979.       else
  980.         this.removeAttribute('placeholder-text');
  981.  
  982.       this.checkForEmpty_();
  983.     },
  984.  
  985.     /**
  986.      * Returns the input element in this text field.
  987.      * @type {HTMLElement} The element that is the actual input field.
  988.      */
  989.     get editField() {
  990.       return this.editField_;
  991.     },
  992.  
  993.     /**
  994.      * Whether the user is currently editing the list item.
  995.      * @type {boolean}
  996.      */
  997.     get editing() {
  998.       return this.hasAttribute('editing');
  999.     },
  1000.     set editing(editing) {
  1001.       if (this.editing == editing)
  1002.         return;
  1003.  
  1004.       if (editing)
  1005.         this.setAttribute('editing', '');
  1006.       else
  1007.         this.removeAttribute('editing');
  1008.  
  1009.       if (editing) {
  1010.         this.editCanceled_ = false;
  1011.  
  1012.         if (this.empty) {
  1013.           this.removeAttribute('empty');
  1014.           if (this.editField)
  1015.             this.editField.value = '';
  1016.         }
  1017.         if (this.editField) {
  1018.           this.editField.focus();
  1019.           this.editField.select();
  1020.         }
  1021.       } else {
  1022.         if (!this.editCanceled_ && this.hasBeenEdited &&
  1023.             this.currentInputIsValid) {
  1024.           this.updateStaticValues_();
  1025.           cr.dispatchSimpleEvent(this, 'commitedit', true);
  1026.         } else {
  1027.           this.resetEditableValues_();
  1028.           cr.dispatchSimpleEvent(this, 'canceledit', true);
  1029.         }
  1030.         this.checkForEmpty_();
  1031.       }
  1032.     },
  1033.  
  1034.     /**
  1035.      * Whether the item is editable.
  1036.      * @type {boolean}
  1037.      */
  1038.     get editable() {
  1039.       return this.hasAttribute('editable');
  1040.     },
  1041.     set editable(editable) {
  1042.       if (this.editable == editable)
  1043.         return;
  1044.  
  1045.       if (editable)
  1046.         this.setAttribute('editable', '');
  1047.       else
  1048.         this.removeAttribute('editable');
  1049.       this.editable_ = editable;
  1050.     },
  1051.  
  1052.     /**
  1053.      * The data model for this field.
  1054.      * @type {Object}
  1055.      */
  1056.     get model() {
  1057.       return this.model_;
  1058.     },
  1059.     set model(model) {
  1060.       this.model_ = model;
  1061.       this.checkForEmpty_();  // This also updates the editField value.
  1062.       this.updateStaticValues_();
  1063.     },
  1064.  
  1065.     /**
  1066.      * The HTML element that should have focus initially when editing starts,
  1067.      * if a specific element wasn't clicked. Defaults to the first <input>
  1068.      * element; can be overridden by subclasses if a different element should be
  1069.      * focused.
  1070.      * @type {?HTMLElement}
  1071.      */
  1072.     get initialFocusElement() {
  1073.       return this.querySelector('input');
  1074.     },
  1075.  
  1076.     /**
  1077.      * Whether the input in currently valid to submit. If this returns false
  1078.      * when editing would be submitted, either editing will not be ended,
  1079.      * or it will be cancelled, depending on the context. Can be overridden by
  1080.      * subclasses to perform input validation.
  1081.      * @type {boolean}
  1082.      */
  1083.     get currentInputIsValid() {
  1084.       return true;
  1085.     },
  1086.  
  1087.     /**
  1088.      * Returns true if the item has been changed by an edit. Can be overridden
  1089.      * by subclasses to return false when nothing has changed to avoid
  1090.      * unnecessary commits.
  1091.      * @type {boolean}
  1092.      */
  1093.     get hasBeenEdited() {
  1094.       return true;
  1095.     },
  1096.  
  1097.     /**
  1098.      * Mutates the input during a successful commit.  Can be overridden to
  1099.      * provide a way to "clean up" valid input so that it conforms to a
  1100.      * desired format.  Will only be called when commit succeeds for valid
  1101.      * input, or when the model is set.
  1102.      * @param {string} value Input text to be mutated.
  1103.      * @return {string} mutated text.
  1104.      */
  1105.     mutateInput: function(value) {
  1106.       return value;
  1107.     },
  1108.  
  1109.     /**
  1110.      * Creates a div containing an <input>, as well as static text, keeping
  1111.      * references to them so they can be manipulated.
  1112.      * @param {string} text The text of the cell.
  1113.      * @private
  1114.      */
  1115.     createEditableTextCell: function(text) {
  1116.       // This function should only be called once.
  1117.       if (this.editField_)
  1118.         return;
  1119.  
  1120.       var container = this.ownerDocument.createElement('div');
  1121.  
  1122.       var textEl = this.ownerDocument.createElement('div');
  1123.       textEl.className = 'static-text';
  1124.       textEl.textContent = text;
  1125.       textEl.setAttribute('displaymode', 'static');
  1126.       this.appendChild(textEl);
  1127.       this.staticText_ = textEl;
  1128.  
  1129.       var inputEl = this.ownerDocument.createElement('input');
  1130.       inputEl.className = 'editable-text';
  1131.       inputEl.type = 'text';
  1132.       inputEl.value = text;
  1133.       inputEl.setAttribute('displaymode', 'edit');
  1134.       inputEl.staticVersion = textEl;
  1135.       this.appendChild(inputEl);
  1136.       this.editField_ = inputEl;
  1137.     },
  1138.  
  1139.     /**
  1140.      * Resets the editable version of any controls created by
  1141.      * createEditableTextCell to match the static text.
  1142.      * @private
  1143.      */
  1144.     resetEditableValues_: function() {
  1145.       var editField = this.editField_;
  1146.       var staticLabel = editField.staticVersion;
  1147.       if (!staticLabel)
  1148.         return;
  1149.  
  1150.       if (editField instanceof HTMLInputElement)
  1151.         editField.value = staticLabel.textContent;
  1152.  
  1153.       editField.setCustomValidity('');
  1154.     },
  1155.  
  1156.     /**
  1157.      * Sets the static version of any controls created by createEditableTextCell
  1158.      * to match the current value of the editable version. Called on commit so
  1159.      * that there's no flicker of the old value before the model updates.  Also
  1160.      * updates the model's value with the mutated value of the edit field.
  1161.      * @private
  1162.      */
  1163.     updateStaticValues_: function() {
  1164.       var editField = this.editField_;
  1165.       var staticLabel = editField.staticVersion;
  1166.       if (!staticLabel)
  1167.         return;
  1168.  
  1169.       if (editField instanceof HTMLInputElement) {
  1170.         staticLabel.textContent = editField.value;
  1171.         this.model_.value = this.mutateInput(editField.value);
  1172.       }
  1173.     },
  1174.  
  1175.     /**
  1176.      * Checks to see if the model or its value are empty.  If they are, then set
  1177.      * the edit field to the placeholder text, if any, and if not, set it to the
  1178.      * model's value.
  1179.      * @private
  1180.      */
  1181.     checkForEmpty_: function() {
  1182.       var editField = this.editField_;
  1183.       if (!editField)
  1184.         return;
  1185.  
  1186.       if (!this.model_ || !this.model_.value) {
  1187.         this.setAttribute('empty', '');
  1188.         editField.value = this.placeholderText || '';
  1189.       } else {
  1190.         this.removeAttribute('empty');
  1191.         editField.value = this.model_.value;
  1192.       }
  1193.     },
  1194.  
  1195.     /**
  1196.      * Called when this widget receives focus.
  1197.      * @param {Event} e the focus event.
  1198.      * @private
  1199.      */
  1200.     handleFocus_: function(e) {
  1201.       if (this.editing)
  1202.         return;
  1203.  
  1204.       this.editing = true;
  1205.       if (this.editField_)
  1206.         this.editField_.focus();
  1207.     },
  1208.  
  1209.     /**
  1210.      * Called when this widget loses focus.
  1211.      * @param {Event} e the blur event.
  1212.      * @private
  1213.      */
  1214.     handleBlur_: function(e) {
  1215.       if (!this.editing)
  1216.         return;
  1217.  
  1218.       this.editing = false;
  1219.     },
  1220.  
  1221.     /**
  1222.      * Called when a key is pressed. Handles committing and canceling edits.
  1223.      * @param {Event} e The key down event.
  1224.      * @private
  1225.      */
  1226.     handleKeyDown_: function(e) {
  1227.       if (!this.editing)
  1228.         return;
  1229.  
  1230.       var endEdit;
  1231.       switch (e.keyIdentifier) {
  1232.         case 'U+001B':  // Esc
  1233.           this.editCanceled_ = true;
  1234.           endEdit = true;
  1235.           break;
  1236.         case 'Enter':
  1237.           if (this.currentInputIsValid)
  1238.             endEdit = true;
  1239.           break;
  1240.       }
  1241.  
  1242.       if (endEdit) {
  1243.         // Blurring will trigger the edit to end.
  1244.         this.ownerDocument.activeElement.blur();
  1245.         // Make sure that handled keys aren't passed on and double-handled.
  1246.         // (e.g., esc shouldn't both cancel an edit and close a subpage)
  1247.         e.stopPropagation();
  1248.       }
  1249.     },
  1250.   };
  1251.  
  1252.   /**
  1253.    * Takes care of committing changes to EditableTextField items when the
  1254.    * window loses focus.
  1255.    */
  1256.   window.addEventListener('blur', function(e) {
  1257.     var itemAncestor = findAncestor(document.activeElement, function(node) {
  1258.       return node instanceof EditableTextField;
  1259.     });
  1260.     if (itemAncestor)
  1261.       document.activeElement.blur();
  1262.   });
  1263.  
  1264.   return {
  1265.     EditableTextField: EditableTextField,
  1266.   };
  1267. });
  1268.  
  1269. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1270. // Use of this source code is governed by a BSD-style license that can be
  1271. // found in the LICENSE file.
  1272.  
  1273. cr.define('options', function() {
  1274.   /** @const */ var DeletableItem = options.DeletableItem;
  1275.   /** @const */ var DeletableItemList = options.DeletableItemList;
  1276.  
  1277.   /**
  1278.    * Creates a new list item with support for inline editing.
  1279.    * @constructor
  1280.    * @extends {options.DeletableListItem}
  1281.    */
  1282.   function InlineEditableItem() {
  1283.     var el = cr.doc.createElement('div');
  1284.     InlineEditableItem.decorate(el);
  1285.     return el;
  1286.   }
  1287.  
  1288.   /**
  1289.    * Decorates an element as a inline-editable list item. Note that this is
  1290.    * a subclass of DeletableItem.
  1291.    * @param {!HTMLElement} el The element to decorate.
  1292.    */
  1293.   InlineEditableItem.decorate = function(el) {
  1294.     el.__proto__ = InlineEditableItem.prototype;
  1295.     el.decorate();
  1296.   };
  1297.  
  1298.   InlineEditableItem.prototype = {
  1299.     __proto__: DeletableItem.prototype,
  1300.  
  1301.     /**
  1302.      * Whether or not this item can be edited.
  1303.      * @type {boolean}
  1304.      * @private
  1305.      */
  1306.     editable_: true,
  1307.  
  1308.     /**
  1309.      * Whether or not this is a placeholder for adding a new item.
  1310.      * @type {boolean}
  1311.      * @private
  1312.      */
  1313.     isPlaceholder_: false,
  1314.  
  1315.     /**
  1316.      * Fields associated with edit mode.
  1317.      * @type {array}
  1318.      * @private
  1319.      */
  1320.     editFields_: null,
  1321.  
  1322.     /**
  1323.      * Whether or not the current edit should be considered cancelled, rather
  1324.      * than committed, when editing ends.
  1325.      * @type {boolean}
  1326.      * @private
  1327.      */
  1328.     editCancelled_: true,
  1329.  
  1330.     /**
  1331.      * The editable item corresponding to the last click, if any. Used to decide
  1332.      * initial focus when entering edit mode.
  1333.      * @type {HTMLElement}
  1334.      * @private
  1335.      */
  1336.     editClickTarget_: null,
  1337.  
  1338.     /** @override */
  1339.     decorate: function() {
  1340.       DeletableItem.prototype.decorate.call(this);
  1341.  
  1342.       this.editFields_ = [];
  1343.       this.addEventListener('mousedown', this.handleMouseDown_);
  1344.       this.addEventListener('keydown', this.handleKeyDown_);
  1345.       this.addEventListener('leadChange', this.handleLeadChange_);
  1346.     },
  1347.  
  1348.     /** @override */
  1349.     selectionChanged: function() {
  1350.       this.updateEditState();
  1351.     },
  1352.  
  1353.     /**
  1354.      * Called when this element gains or loses 'lead' status. Updates editing
  1355.      * mode accordingly.
  1356.      * @private
  1357.      */
  1358.     handleLeadChange_: function() {
  1359.       this.updateEditState();
  1360.     },
  1361.  
  1362.     /**
  1363.      * Updates the edit state based on the current selected and lead states.
  1364.      */
  1365.     updateEditState: function() {
  1366.       if (this.editable)
  1367.         this.editing = this.selected && this.lead;
  1368.     },
  1369.  
  1370.     /**
  1371.      * Whether the user is currently editing the list item.
  1372.      * @type {boolean}
  1373.      */
  1374.     get editing() {
  1375.       return this.hasAttribute('editing');
  1376.     },
  1377.     set editing(editing) {
  1378.       if (this.editing == editing)
  1379.         return;
  1380.  
  1381.       if (editing)
  1382.         this.setAttribute('editing', '');
  1383.       else
  1384.         this.removeAttribute('editing');
  1385.  
  1386.       if (editing) {
  1387.         this.editCancelled_ = false;
  1388.  
  1389.         cr.dispatchSimpleEvent(this, 'edit', true);
  1390.  
  1391.         var focusElement = this.editClickTarget_ || this.initialFocusElement;
  1392.         this.editClickTarget_ = null;
  1393.  
  1394.         // When this is called in response to the selectedChange event,
  1395.         // the list grabs focus immediately afterwards. Thus we must delay
  1396.         // our focus grab.
  1397.         var self = this;
  1398.         if (focusElement) {
  1399.           window.setTimeout(function() {
  1400.             // Make sure we are still in edit mode by the time we execute.
  1401.             if (self.editing) {
  1402.               focusElement.focus();
  1403.               focusElement.select();
  1404.             }
  1405.           }, 50);
  1406.         }
  1407.       } else {
  1408.         if (!this.editCancelled_ && this.hasBeenEdited &&
  1409.             this.currentInputIsValid) {
  1410.           if (this.isPlaceholder)
  1411.             this.parentNode.focusPlaceholder = true;
  1412.  
  1413.           this.updateStaticValues_();
  1414.           cr.dispatchSimpleEvent(this, 'commitedit', true);
  1415.         } else {
  1416.           this.resetEditableValues_();
  1417.           cr.dispatchSimpleEvent(this, 'canceledit', true);
  1418.         }
  1419.       }
  1420.     },
  1421.  
  1422.     /**
  1423.      * Whether the item is editable.
  1424.      * @type {boolean}
  1425.      */
  1426.     get editable() {
  1427.       return this.editable_;
  1428.     },
  1429.     set editable(editable) {
  1430.       this.editable_ = editable;
  1431.       if (!editable)
  1432.         this.editing = false;
  1433.     },
  1434.  
  1435.     /**
  1436.      * Whether the item is a new item placeholder.
  1437.      * @type {boolean}
  1438.      */
  1439.     get isPlaceholder() {
  1440.       return this.isPlaceholder_;
  1441.     },
  1442.     set isPlaceholder(isPlaceholder) {
  1443.       this.isPlaceholder_ = isPlaceholder;
  1444.       if (isPlaceholder)
  1445.         this.deletable = false;
  1446.     },
  1447.  
  1448.     /**
  1449.      * The HTML element that should have focus initially when editing starts,
  1450.      * if a specific element wasn't clicked.
  1451.      * Defaults to the first <input> element; can be overridden by subclasses if
  1452.      * a different element should be focused.
  1453.      * @type {HTMLElement}
  1454.      */
  1455.     get initialFocusElement() {
  1456.       return this.contentElement.querySelector('input');
  1457.     },
  1458.  
  1459.     /**
  1460.      * Whether the input in currently valid to submit. If this returns false
  1461.      * when editing would be submitted, either editing will not be ended,
  1462.      * or it will be cancelled, depending on the context.
  1463.      * Can be overridden by subclasses to perform input validation.
  1464.      * @type {boolean}
  1465.      */
  1466.     get currentInputIsValid() {
  1467.       return true;
  1468.     },
  1469.  
  1470.     /**
  1471.      * Returns true if the item has been changed by an edit.
  1472.      * Can be overridden by subclasses to return false when nothing has changed
  1473.      * to avoid unnecessary commits.
  1474.      * @type {boolean}
  1475.      */
  1476.     get hasBeenEdited() {
  1477.       return true;
  1478.     },
  1479.  
  1480.     /**
  1481.      * Returns a div containing an <input>, as well as static text if
  1482.      * isPlaceholder is not true.
  1483.      * @param {string} text The text of the cell.
  1484.      * @return {HTMLElement} The HTML element for the cell.
  1485.      * @private
  1486.      */
  1487.     createEditableTextCell: function(text) {
  1488.       var container = this.ownerDocument.createElement('div');
  1489.  
  1490.       if (!this.isPlaceholder) {
  1491.         var textEl = this.ownerDocument.createElement('div');
  1492.         textEl.className = 'static-text';
  1493.         textEl.textContent = text;
  1494.         textEl.setAttribute('displaymode', 'static');
  1495.         container.appendChild(textEl);
  1496.       }
  1497.  
  1498.       var inputEl = this.ownerDocument.createElement('input');
  1499.       inputEl.type = 'text';
  1500.       inputEl.value = text;
  1501.       if (!this.isPlaceholder) {
  1502.         inputEl.setAttribute('displaymode', 'edit');
  1503.         inputEl.staticVersion = textEl;
  1504.       } else {
  1505.         // At this point |this| is not attached to the parent list yet, so give
  1506.         // a short timeout in order for the attachment to occur.
  1507.         var self = this;
  1508.         window.setTimeout(function() {
  1509.           var list = self.parentNode;
  1510.           if (list && list.focusPlaceholder) {
  1511.             list.focusPlaceholder = false;
  1512.             if (list.shouldFocusPlaceholder())
  1513.               inputEl.focus();
  1514.           }
  1515.         }, 50);
  1516.       }
  1517.  
  1518.       inputEl.addEventListener('focus', this.handleFocus_.bind(this));
  1519.       container.appendChild(inputEl);
  1520.       this.editFields_.push(inputEl);
  1521.  
  1522.       return container;
  1523.     },
  1524.  
  1525.     /**
  1526.      * Resets the editable version of any controls created by createEditable*
  1527.      * to match the static text.
  1528.      * @private
  1529.      */
  1530.     resetEditableValues_: function() {
  1531.       var editFields = this.editFields_;
  1532.       for (var i = 0; i < editFields.length; i++) {
  1533.         var staticLabel = editFields[i].staticVersion;
  1534.         if (!staticLabel && !this.isPlaceholder)
  1535.           continue;
  1536.  
  1537.         if (editFields[i].tagName == 'INPUT') {
  1538.           editFields[i].value =
  1539.             this.isPlaceholder ? '' : staticLabel.textContent;
  1540.         }
  1541.         // Add more tag types here as new createEditable* methods are added.
  1542.  
  1543.         editFields[i].setCustomValidity('');
  1544.       }
  1545.     },
  1546.  
  1547.     /**
  1548.      * Sets the static version of any controls created by createEditable*
  1549.      * to match the current value of the editable version. Called on commit so
  1550.      * that there's no flicker of the old value before the model updates.
  1551.      * @private
  1552.      */
  1553.     updateStaticValues_: function() {
  1554.       var editFields = this.editFields_;
  1555.       for (var i = 0; i < editFields.length; i++) {
  1556.         var staticLabel = editFields[i].staticVersion;
  1557.         if (!staticLabel)
  1558.           continue;
  1559.  
  1560.         if (editFields[i].tagName == 'INPUT')
  1561.           staticLabel.textContent = editFields[i].value;
  1562.         // Add more tag types here as new createEditable* methods are added.
  1563.       }
  1564.     },
  1565.  
  1566.     /**
  1567.      * Called when a key is pressed. Handles committing and canceling edits.
  1568.      * @param {Event} e The key down event.
  1569.      * @private
  1570.      */
  1571.     handleKeyDown_: function(e) {
  1572.       if (!this.editing)
  1573.         return;
  1574.  
  1575.       var endEdit = false;
  1576.       switch (e.keyIdentifier) {
  1577.         case 'U+001B':  // Esc
  1578.           this.editCancelled_ = true;
  1579.           endEdit = true;
  1580.           break;
  1581.         case 'Enter':
  1582.           if (this.currentInputIsValid)
  1583.             endEdit = true;
  1584.           break;
  1585.       }
  1586.  
  1587.       if (endEdit) {
  1588.         // Blurring will trigger the edit to end; see InlineEditableItemList.
  1589.         this.ownerDocument.activeElement.blur();
  1590.         // Make sure that handled keys aren't passed on and double-handled.
  1591.         // (e.g., esc shouldn't both cancel an edit and close a subpage)
  1592.         e.stopPropagation();
  1593.       }
  1594.     },
  1595.  
  1596.     /**
  1597.      * Called when the list item is clicked. If the click target corresponds to
  1598.      * an editable item, stores that item to focus when edit mode is started.
  1599.      * @param {Event} e The mouse down event.
  1600.      * @private
  1601.      */
  1602.     handleMouseDown_: function(e) {
  1603.       if (!this.editable || this.editing)
  1604.         return;
  1605.  
  1606.       var clickTarget = e.target;
  1607.       var editFields = this.editFields_;
  1608.       for (var i = 0; i < editFields.length; i++) {
  1609.         if (editFields[i] == clickTarget ||
  1610.             editFields[i].staticVersion == clickTarget) {
  1611.           this.editClickTarget_ = editFields[i];
  1612.           return;
  1613.         }
  1614.       }
  1615.     },
  1616.   };
  1617.  
  1618.   /**
  1619.    * Takes care of committing changes to inline editable list items when the
  1620.    * window loses focus.
  1621.    */
  1622.   function handleWindowBlurs() {
  1623.     window.addEventListener('blur', function(e) {
  1624.       var itemAncestor = findAncestor(document.activeElement, function(node) {
  1625.         return node instanceof InlineEditableItem;
  1626.       });
  1627.       if (itemAncestor)
  1628.         document.activeElement.blur();
  1629.     });
  1630.   }
  1631.   handleWindowBlurs();
  1632.  
  1633.   var InlineEditableItemList = cr.ui.define('list');
  1634.  
  1635.   InlineEditableItemList.prototype = {
  1636.     __proto__: DeletableItemList.prototype,
  1637.  
  1638.     /**
  1639.      * Focuses the input element of the placeholder if true.
  1640.      * @type {boolean}
  1641.      */
  1642.     focusPlaceholder: false,
  1643.  
  1644.     /** @override */
  1645.     decorate: function() {
  1646.       DeletableItemList.prototype.decorate.call(this);
  1647.       this.setAttribute('inlineeditable', '');
  1648.       this.addEventListener('hasElementFocusChange',
  1649.                             this.handleListFocusChange_);
  1650.     },
  1651.  
  1652.     /**
  1653.      * Called when the list hierarchy as a whole loses or gains focus; starts
  1654.      * or ends editing for the lead item if necessary.
  1655.      * @param {Event} e The change event.
  1656.      * @private
  1657.      */
  1658.     handleListFocusChange_: function(e) {
  1659.       var leadItem = this.getListItemByIndex(this.selectionModel.leadIndex);
  1660.       if (leadItem) {
  1661.         if (e.newValue)
  1662.           leadItem.updateEditState();
  1663.         else
  1664.           leadItem.editing = false;
  1665.       }
  1666.     },
  1667.  
  1668.     /**
  1669.      * May be overridden by subclasses to disable focusing the placeholder.
  1670.      * @return {boolean} True if the placeholder element should be focused on
  1671.      *     edit commit.
  1672.      */
  1673.     shouldFocusPlaceholder: function() {
  1674.       return true;
  1675.     },
  1676.   };
  1677.  
  1678.   // Export
  1679.   return {
  1680.     InlineEditableItem: InlineEditableItem,
  1681.     InlineEditableItemList: InlineEditableItemList,
  1682.   };
  1683. });
  1684.  
  1685. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  1686. // Use of this source code is governed by a BSD-style license that can be
  1687. // found in the LICENSE file.
  1688.  
  1689. cr.define('options', function() {
  1690.   /////////////////////////////////////////////////////////////////////////////
  1691.   // OptionsPage class:
  1692.  
  1693.   /**
  1694.    * Base class for options page.
  1695.    * @constructor
  1696.    * @param {string} name Options page name.
  1697.    * @param {string} title Options page title, used for history.
  1698.    * @extends {EventTarget}
  1699.    */
  1700.   function OptionsPage(name, title, pageDivName) {
  1701.     this.name = name;
  1702.     this.title = title;
  1703.     this.pageDivName = pageDivName;
  1704.     this.pageDiv = $(this.pageDivName);
  1705.     this.tab = null;
  1706.     this.lastFocusedElement = null;
  1707.   }
  1708.  
  1709.   /**
  1710.    * This is the absolute difference maintained between standard and
  1711.    * fixed-width font sizes. Refer http://crbug.com/91922.
  1712.    * @const
  1713.    */
  1714.   OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD = 3;
  1715.  
  1716.   /**
  1717.    * Offset of page container in pixels, to allow room for side menu.
  1718.    * Simplified settings pages can override this if they don't use the menu.
  1719.    * The default (155) comes from -webkit-margin-start in uber_shared.css
  1720.    * @private
  1721.    */
  1722.   OptionsPage.horizontalOffset = 155;
  1723.  
  1724.   /**
  1725.    * Main level option pages. Maps lower-case page names to the respective page
  1726.    * object.
  1727.    * @protected
  1728.    */
  1729.   OptionsPage.registeredPages = {};
  1730.  
  1731.   /**
  1732.    * Pages which are meant to behave like modal dialogs. Maps lower-case overlay
  1733.    * names to the respective overlay object.
  1734.    * @protected
  1735.    */
  1736.   OptionsPage.registeredOverlayPages = {};
  1737.  
  1738.   /**
  1739.    * Gets the default page (to be shown on initial load).
  1740.    */
  1741.   OptionsPage.getDefaultPage = function() {
  1742.     return BrowserOptions.getInstance();
  1743.   };
  1744.  
  1745.   /**
  1746.    * Shows the default page.
  1747.    */
  1748.   OptionsPage.showDefaultPage = function() {
  1749.     this.navigateToPage(this.getDefaultPage().name);
  1750.   };
  1751.  
  1752.   /**
  1753.    * "Navigates" to a page, meaning that the page will be shown and the
  1754.    * appropriate entry is placed in the history.
  1755.    * @param {string} pageName Page name.
  1756.    */
  1757.   OptionsPage.navigateToPage = function(pageName) {
  1758.     this.showPageByName(pageName, true);
  1759.   };
  1760.  
  1761.   /**
  1762.    * Shows a registered page. This handles both top-level and overlay pages.
  1763.    * @param {string} pageName Page name.
  1764.    * @param {boolean} updateHistory True if we should update the history after
  1765.    *     showing the page.
  1766.    * @param {Object=} opt_propertyBag An optional bag of properties including
  1767.    *     replaceState (if history state should be replaced instead of pushed).
  1768.    * @private
  1769.    */
  1770.   OptionsPage.showPageByName = function(pageName,
  1771.                                         updateHistory,
  1772.                                         opt_propertyBag) {
  1773.     // If |opt_propertyBag| is non-truthy, homogenize to object.
  1774.     opt_propertyBag = opt_propertyBag || {};
  1775.  
  1776.     // If a bubble is currently being shown, hide it.
  1777.     this.hideBubble();
  1778.  
  1779.     // Find the currently visible root-level page.
  1780.     var rootPage = null;
  1781.     for (var name in this.registeredPages) {
  1782.       var page = this.registeredPages[name];
  1783.       if (page.visible && !page.parentPage) {
  1784.         rootPage = page;
  1785.         break;
  1786.       }
  1787.     }
  1788.  
  1789.     // Find the target page.
  1790.     var targetPage = this.registeredPages[pageName.toLowerCase()];
  1791.     if (!targetPage || !targetPage.canShowPage()) {
  1792.       // If it's not a page, try it as an overlay.
  1793.       if (!targetPage && this.showOverlay_(pageName, rootPage)) {
  1794.         if (updateHistory)
  1795.           this.updateHistoryState_(!!opt_propertyBag.replaceState);
  1796.         return;
  1797.       } else {
  1798.         targetPage = this.getDefaultPage();
  1799.       }
  1800.     }
  1801.  
  1802.     pageName = targetPage.name.toLowerCase();
  1803.     var targetPageWasVisible = targetPage.visible;
  1804.  
  1805.     // Determine if the root page is 'sticky', meaning that it
  1806.     // shouldn't change when showing an overlay. This can happen for special
  1807.     // pages like Search.
  1808.     var isRootPageLocked =
  1809.         rootPage && rootPage.sticky && targetPage.parentPage;
  1810.  
  1811.     var allPageNames = Array.prototype.concat.call(
  1812.         Object.keys(this.registeredPages),
  1813.         Object.keys(this.registeredOverlayPages));
  1814.  
  1815.     // Notify pages if they will be hidden.
  1816.     for (var i = 0; i < allPageNames.length; ++i) {
  1817.       var name = allPageNames[i];
  1818.       var page = this.registeredPages[name] ||
  1819.                  this.registeredOverlayPages[name];
  1820.       if (!page.parentPage && isRootPageLocked)
  1821.         continue;
  1822.       if (page.willHidePage && name != pageName &&
  1823.           !page.isAncestorOfPage(targetPage)) {
  1824.         page.willHidePage();
  1825.       }
  1826.     }
  1827.  
  1828.     // Update visibilities to show only the hierarchy of the target page.
  1829.     for (var i = 0; i < allPageNames.length; ++i) {
  1830.       var name = allPageNames[i];
  1831.       var page = this.registeredPages[name] ||
  1832.                  this.registeredOverlayPages[name];
  1833.       if (!page.parentPage && isRootPageLocked)
  1834.         continue;
  1835.       page.visible = name == pageName || page.isAncestorOfPage(targetPage);
  1836.     }
  1837.  
  1838.     // Update the history and current location.
  1839.     if (updateHistory)
  1840.       this.updateHistoryState_(!!opt_propertyBag.replaceState);
  1841.  
  1842.     // Update tab title.
  1843.     this.setTitle_(targetPage.title);
  1844.  
  1845.     // Update focus if any other control was focused before.
  1846.     if (document.activeElement != document.body)
  1847.       targetPage.focus();
  1848.  
  1849.     // Notify pages if they were shown.
  1850.     for (var i = 0; i < allPageNames.length; ++i) {
  1851.       var name = allPageNames[i];
  1852.       var page = this.registeredPages[name] ||
  1853.                  this.registeredOverlayPages[name];
  1854.       if (!page.parentPage && isRootPageLocked)
  1855.         continue;
  1856.       if (!targetPageWasVisible && page.didShowPage &&
  1857.           (name == pageName || page.isAncestorOfPage(targetPage))) {
  1858.         page.didShowPage();
  1859.       }
  1860.     }
  1861.   };
  1862.  
  1863.   /**
  1864.    * Sets the title of the page. This is accomplished by calling into the
  1865.    * parent page API.
  1866.    * @param {String} title The title string.
  1867.    * @private
  1868.    */
  1869.   OptionsPage.setTitle_ = function(title) {
  1870.     uber.invokeMethodOnParent('setTitle', {title: title});
  1871.   };
  1872.  
  1873.   /**
  1874.    * Scrolls the page to the correct position (the top when opening an overlay,
  1875.    * or the old scroll position a previously hidden overlay becomes visible).
  1876.    * @private
  1877.    */
  1878.   OptionsPage.updateScrollPosition_ = function() {
  1879.     var container = $('page-container');
  1880.     var scrollTop = container.oldScrollTop || 0;
  1881.     container.oldScrollTop = undefined;
  1882.     window.scroll(document.body.scrollLeft, scrollTop);
  1883.   };
  1884.  
  1885.   /**
  1886.    * Pushes the current page onto the history stack, overriding the last page
  1887.    * if it is the generic chrome://settings/.
  1888.    * @param {boolean} replace If true, allow no history events to be created.
  1889.    * @param {object=} opt_params A bag of optional params, including:
  1890.    *     {boolean} ignoreHash Whether to include the hash or not.
  1891.    * @private
  1892.    */
  1893.   OptionsPage.updateHistoryState_ = function(replace, opt_params) {
  1894.     var page = this.getTopmostVisiblePage();
  1895.     var path = window.location.pathname + window.location.hash;
  1896.     if (path)
  1897.       path = path.slice(1).replace(/\/(?:#|$)/, '');  // Remove trailing slash.
  1898.  
  1899.     // Update tab title.
  1900.     this.setTitle_(page.title);
  1901.  
  1902.     // The page is already in history (the user may have clicked the same link
  1903.     // twice). Do nothing.
  1904.     if (path == page.name &&
  1905.         !document.documentElement.classList.contains('loading')) {
  1906.       return;
  1907.     }
  1908.  
  1909.     var hash = opt_params && opt_params.ignoreHash ? '' : window.location.hash;
  1910.  
  1911.     // If settings are embedded, tell the outer page to set its "path" to the
  1912.     // inner frame's path.
  1913.     var outerPath = (page == this.getDefaultPage() ? '' : page.name) + hash;
  1914.     uber.invokeMethodOnParent('setPath', {path: outerPath});
  1915.  
  1916.     // If there is no path, the current location is chrome://settings/.
  1917.     // Override this with the new page.
  1918.     var historyFunction = path && !replace ? window.history.pushState :
  1919.                                              window.history.replaceState;
  1920.     historyFunction.call(window.history,
  1921.                          {pageName: page.name},
  1922.                          page.title,
  1923.                          '/' + page.name + hash);
  1924.   };
  1925.  
  1926.   /**
  1927.    * Shows a registered Overlay page. Does not update history.
  1928.    * @param {string} overlayName Page name.
  1929.    * @param {OptionPage} rootPage The currently visible root-level page.
  1930.    * @return {boolean} whether we showed an overlay.
  1931.    */
  1932.   OptionsPage.showOverlay_ = function(overlayName, rootPage) {
  1933.     var overlay = this.registeredOverlayPages[overlayName.toLowerCase()];
  1934.     if (!overlay || !overlay.canShowPage())
  1935.       return false;
  1936.  
  1937.     // Save the currently focused element in the page for restoration later.
  1938.     var currentPage = this.getTopmostVisiblePage();
  1939.     if (currentPage)
  1940.       currentPage.lastFocusedElement = document.activeElement;
  1941.  
  1942.     if ((!rootPage || !rootPage.sticky) && overlay.parentPage)
  1943.       this.showPageByName(overlay.parentPage.name, false);
  1944.  
  1945.     if (!overlay.visible) {
  1946.       overlay.visible = true;
  1947.       if (overlay.didShowPage) overlay.didShowPage();
  1948.     }
  1949.  
  1950.     // Update tab title.
  1951.     this.setTitle_(overlay.title);
  1952.  
  1953.     // Change focus to the overlay if any other control was focused before.
  1954.     if (document.activeElement != document.body)
  1955.       overlay.focus();
  1956.  
  1957.     $('searchBox').setAttribute('aria-hidden', true);
  1958.  
  1959.     if ($('search-field').value == '') {
  1960.       var section = overlay.associatedSection;
  1961.       if (section)
  1962.         options.BrowserOptions.scrollToSection(section);
  1963.     }
  1964.  
  1965.     return true;
  1966.   };
  1967.  
  1968.   /**
  1969.    * Returns whether or not an overlay is visible.
  1970.    * @return {boolean} True if an overlay is visible.
  1971.    * @private
  1972.    */
  1973.   OptionsPage.isOverlayVisible_ = function() {
  1974.     return this.getVisibleOverlay_() != null;
  1975.   };
  1976.  
  1977.   /**
  1978.    * Returns the currently visible overlay, or null if no page is visible.
  1979.    * @return {OptionPage} The visible overlay.
  1980.    */
  1981.   OptionsPage.getVisibleOverlay_ = function() {
  1982.     var topmostPage = null;
  1983.     for (var name in this.registeredOverlayPages) {
  1984.       var page = this.registeredOverlayPages[name];
  1985.       if (page.visible &&
  1986.           (!topmostPage || page.nestingLevel > topmostPage.nestingLevel)) {
  1987.         topmostPage = page;
  1988.       }
  1989.     }
  1990.     return topmostPage;
  1991.   };
  1992.  
  1993.   /**
  1994.    * Restores the last focused element on a given page.
  1995.    */
  1996.   OptionsPage.restoreLastFocusedElement_ = function() {
  1997.     var currentPage = this.getTopmostVisiblePage();
  1998.     if (currentPage.lastFocusedElement)
  1999.       currentPage.lastFocusedElement.focus();
  2000.   };
  2001.  
  2002.   /**
  2003.    * Closes the visible overlay. Updates the history state after closing the
  2004.    * overlay.
  2005.    */
  2006.   OptionsPage.closeOverlay = function() {
  2007.     var overlay = this.getVisibleOverlay_();
  2008.     if (!overlay)
  2009.       return;
  2010.  
  2011.     overlay.visible = false;
  2012.  
  2013.     if (overlay.didClosePage) overlay.didClosePage();
  2014.     this.updateHistoryState_(false, {ignoreHash: true});
  2015.  
  2016.     this.restoreLastFocusedElement_();
  2017.     if (!this.isOverlayVisible_())
  2018.       $('searchBox').removeAttribute('aria-hidden');
  2019.   };
  2020.  
  2021.   /**
  2022.    * Cancels (closes) the overlay, due to the user pressing <Esc>.
  2023.    */
  2024.   OptionsPage.cancelOverlay = function() {
  2025.     // Blur the active element to ensure any changed pref value is saved.
  2026.     document.activeElement.blur();
  2027.     var overlay = this.getVisibleOverlay_();
  2028.     // Let the overlay handle the <Esc> if it wants to.
  2029.     if (overlay.handleCancel) {
  2030.       overlay.handleCancel();
  2031.       this.restoreLastFocusedElement_();
  2032.     } else {
  2033.       this.closeOverlay();
  2034.     }
  2035.   };
  2036.  
  2037.   /**
  2038.    * Hides the visible overlay. Does not affect the history state.
  2039.    * @private
  2040.    */
  2041.   OptionsPage.hideOverlay_ = function() {
  2042.     var overlay = this.getVisibleOverlay_();
  2043.     if (overlay)
  2044.       overlay.visible = false;
  2045.   };
  2046.  
  2047.   /**
  2048.    * Returns the pages which are currently visible, ordered by nesting level
  2049.    * (ascending).
  2050.    * @return {Array.OptionPage} The pages which are currently visible, ordered
  2051.    * by nesting level (ascending).
  2052.    */
  2053.   OptionsPage.getVisiblePages_ = function() {
  2054.     var visiblePages = [];
  2055.     for (var name in this.registeredPages) {
  2056.       var page = this.registeredPages[name];
  2057.       if (page.visible)
  2058.         visiblePages[page.nestingLevel] = page;
  2059.     }
  2060.     return visiblePages;
  2061.   };
  2062.  
  2063.   /**
  2064.    * Returns the topmost visible page (overlays excluded).
  2065.    * @return {OptionPage} The topmost visible page aside any overlay.
  2066.    * @private
  2067.    */
  2068.   OptionsPage.getTopmostVisibleNonOverlayPage_ = function() {
  2069.     var topPage = null;
  2070.     for (var name in this.registeredPages) {
  2071.       var page = this.registeredPages[name];
  2072.       if (page.visible &&
  2073.           (!topPage || page.nestingLevel > topPage.nestingLevel))
  2074.         topPage = page;
  2075.     }
  2076.  
  2077.     return topPage;
  2078.   };
  2079.  
  2080.   /**
  2081.    * Returns the topmost visible page, or null if no page is visible.
  2082.    * @return {OptionPage} The topmost visible page.
  2083.    */
  2084.   OptionsPage.getTopmostVisiblePage = function() {
  2085.     // Check overlays first since they're top-most if visible.
  2086.     return this.getVisibleOverlay_() || this.getTopmostVisibleNonOverlayPage_();
  2087.   };
  2088.  
  2089.   /**
  2090.    * Returns the currently visible bubble, or null if no bubble is visible.
  2091.    * @return {OptionsBubble} The bubble currently being shown.
  2092.    */
  2093.   OptionsPage.getVisibleBubble = function() {
  2094.     var bubble = OptionsPage.bubble_;
  2095.     return bubble && !bubble.hidden ? bubble : null;
  2096.   };
  2097.  
  2098.   /**
  2099.    * Shows an informational bubble displaying |content| and pointing at the
  2100.    * |target| element. If |content| has focusable elements, they join the
  2101.    * current page's tab order as siblings of |domSibling|.
  2102.    * @param {HTMLDivElement} content The content of the bubble.
  2103.    * @param {HTMLElement} target The element at which the bubble points.
  2104.    * @param {HTMLElement} domSibling The element after which the bubble is added
  2105.    *                      to the DOM.
  2106.    * @param {cr.ui.ArrowLocation} location The arrow location.
  2107.    */
  2108.   OptionsPage.showBubble = function(content, target, domSibling, location) {
  2109.     OptionsPage.hideBubble();
  2110.  
  2111.     var bubble = new options.OptionsBubble;
  2112.     bubble.anchorNode = target;
  2113.     bubble.domSibling = domSibling;
  2114.     bubble.arrowLocation = location;
  2115.     bubble.content = content;
  2116.     bubble.show();
  2117.     OptionsPage.bubble_ = bubble;
  2118.   };
  2119.  
  2120.   /**
  2121.    * Hides the currently visible bubble, if any.
  2122.    */
  2123.   OptionsPage.hideBubble = function() {
  2124.     if (OptionsPage.bubble_)
  2125.       OptionsPage.bubble_.hide();
  2126.   };
  2127.  
  2128.   /**
  2129.    * Shows the tab contents for the given navigation tab.
  2130.    * @param {!Element} tab The tab that the user clicked.
  2131.    */
  2132.   OptionsPage.showTab = function(tab) {
  2133.     // Search parents until we find a tab, or the nav bar itself. This allows
  2134.     // tabs to have child nodes, e.g. labels in separately-styled spans.
  2135.     while (tab && !tab.classList.contains('subpages-nav-tabs') &&
  2136.            !tab.classList.contains('tab')) {
  2137.       tab = tab.parentNode;
  2138.     }
  2139.     if (!tab || !tab.classList.contains('tab'))
  2140.       return;
  2141.  
  2142.     // Find tab bar of the tab.
  2143.     var tabBar = tab;
  2144.     while (tabBar && !tabBar.classList.contains('subpages-nav-tabs')) {
  2145.       tabBar = tabBar.parentNode;
  2146.     }
  2147.     if (!tabBar)
  2148.       return;
  2149.  
  2150.     if (tabBar.activeNavTab != null) {
  2151.       tabBar.activeNavTab.classList.remove('active-tab');
  2152.       $(tabBar.activeNavTab.getAttribute('tab-contents')).classList.
  2153.           remove('active-tab-contents');
  2154.     }
  2155.  
  2156.     tab.classList.add('active-tab');
  2157.     $(tab.getAttribute('tab-contents')).classList.add('active-tab-contents');
  2158.     tabBar.activeNavTab = tab;
  2159.   };
  2160.  
  2161.   /**
  2162.    * Registers new options page.
  2163.    * @param {OptionsPage} page Page to register.
  2164.    */
  2165.   OptionsPage.register = function(page) {
  2166.     this.registeredPages[page.name.toLowerCase()] = page;
  2167.     page.initializePage();
  2168.   };
  2169.  
  2170.   /**
  2171.    * Find an enclosing section for an element if it exists.
  2172.    * @param {Element} element Element to search.
  2173.    * @return {OptionPage} The section element, or null.
  2174.    * @private
  2175.    */
  2176.   OptionsPage.findSectionForNode_ = function(node) {
  2177.     while (node = node.parentNode) {
  2178.       if (node.nodeName == 'SECTION')
  2179.         return node;
  2180.     }
  2181.     return null;
  2182.   };
  2183.  
  2184.   /**
  2185.    * Registers a new Overlay page.
  2186.    * @param {OptionsPage} overlay Overlay to register.
  2187.    * @param {OptionsPage} parentPage Associated parent page for this overlay.
  2188.    * @param {Array} associatedControls Array of control elements associated with
  2189.    *   this page.
  2190.    */
  2191.   OptionsPage.registerOverlay = function(overlay,
  2192.                                          parentPage,
  2193.                                          associatedControls) {
  2194.     this.registeredOverlayPages[overlay.name.toLowerCase()] = overlay;
  2195.     overlay.parentPage = parentPage;
  2196.     if (associatedControls) {
  2197.       overlay.associatedControls = associatedControls;
  2198.       if (associatedControls.length) {
  2199.         overlay.associatedSection =
  2200.             this.findSectionForNode_(associatedControls[0]);
  2201.       }
  2202.  
  2203.       // Sanity check.
  2204.       for (var i = 0; i < associatedControls.length; ++i) {
  2205.         assert(associatedControls[i], 'Invalid element passed.');
  2206.       }
  2207.     }
  2208.  
  2209.     // Reverse the button strip for views. See the documentation of
  2210.     // reverseButtonStrip_() for an explanation of why this is necessary.
  2211.     if (cr.isViews)
  2212.       this.reverseButtonStrip_(overlay);
  2213.  
  2214.     overlay.tab = undefined;
  2215.     overlay.isOverlay = true;
  2216.     overlay.initializePage();
  2217.   };
  2218.  
  2219.   /**
  2220.    * Reverses the child elements of a button strip. This is necessary because
  2221.    * WebKit does not alter the tab order for elements that are visually reversed
  2222.    * using -webkit-box-direction: reverse, and the button order is reversed for
  2223.    * views.  See https://bugs.webkit.org/show_bug.cgi?id=62664 for more
  2224.    * information.
  2225.    * @param {Object} overlay The overlay containing the button strip to reverse.
  2226.    * @private
  2227.    */
  2228.   OptionsPage.reverseButtonStrip_ = function(overlay) {
  2229.     var buttonStrips = overlay.pageDiv.querySelectorAll('.button-strip');
  2230.  
  2231.     // Reverse all button-strips in the overlay.
  2232.     for (var j = 0; j < buttonStrips.length; j++) {
  2233.       var buttonStrip = buttonStrips[j];
  2234.  
  2235.       var childNodes = buttonStrip.childNodes;
  2236.       for (var i = childNodes.length - 1; i >= 0; i--)
  2237.         buttonStrip.appendChild(childNodes[i]);
  2238.     }
  2239.   };
  2240.  
  2241.   /**
  2242.    * Callback for window.onpopstate.
  2243.    * @param {Object} data State data pushed into history.
  2244.    */
  2245.   OptionsPage.setState = function(data) {
  2246.     if (data && data.pageName) {
  2247.       this.willClose();
  2248.       this.showPageByName(data.pageName, false);
  2249.     }
  2250.   };
  2251.  
  2252.   /**
  2253.    * Callback for window.onbeforeunload. Used to notify overlays that they will
  2254.    * be closed.
  2255.    */
  2256.   OptionsPage.willClose = function() {
  2257.     var overlay = this.getVisibleOverlay_();
  2258.     if (overlay && overlay.didClosePage)
  2259.       overlay.didClosePage();
  2260.   };
  2261.  
  2262.   /**
  2263.    * Freezes/unfreezes the scroll position of the root page container.
  2264.    * @param {boolean} freeze Whether the page should be frozen.
  2265.    * @private
  2266.    */
  2267.   OptionsPage.setRootPageFrozen_ = function(freeze) {
  2268.     var container = $('page-container');
  2269.     if (container.classList.contains('frozen') == freeze)
  2270.       return;
  2271.  
  2272.     if (freeze) {
  2273.       // Lock the width, since auto width computation may change.
  2274.       container.style.width = window.getComputedStyle(container).width;
  2275.       container.oldScrollTop = document.body.scrollTop;
  2276.       container.classList.add('frozen');
  2277.       var verticalPosition =
  2278.           container.getBoundingClientRect().top - container.oldScrollTop;
  2279.       container.style.top = verticalPosition + 'px';
  2280.       this.updateFrozenElementHorizontalPosition_(container);
  2281.     } else {
  2282.       container.classList.remove('frozen');
  2283.       container.style.top = '';
  2284.       container.style.left = '';
  2285.       container.style.right = '';
  2286.       container.style.width = '';
  2287.     }
  2288.   };
  2289.  
  2290.   /**
  2291.    * Freezes/unfreezes the scroll position of the root page based on the current
  2292.    * page stack.
  2293.    */
  2294.   OptionsPage.updateRootPageFreezeState = function() {
  2295.     var topPage = OptionsPage.getTopmostVisiblePage();
  2296.     if (topPage)
  2297.       this.setRootPageFrozen_(topPage.isOverlay);
  2298.   };
  2299.  
  2300.   /**
  2301.    * Initializes the complete options page.  This will cause all C++ handlers to
  2302.    * be invoked to do final setup.
  2303.    */
  2304.   OptionsPage.initialize = function() {
  2305.     chrome.send('coreOptionsInitialize');
  2306.     uber.onContentFrameLoaded();
  2307.  
  2308.     document.addEventListener('scroll', this.handleScroll_.bind(this));
  2309.  
  2310.     // Trigger the scroll handler manually to set the initial state.
  2311.     this.handleScroll_();
  2312.  
  2313.     // Shake the dialog if the user clicks outside the dialog bounds.
  2314.     var containers = [$('overlay-container-1'), $('overlay-container-2')];
  2315.     for (var i = 0; i < containers.length; i++) {
  2316.       var overlay = containers[i];
  2317.       cr.ui.overlay.setupOverlay(overlay);
  2318.       overlay.addEventListener('cancelOverlay',
  2319.                                OptionsPage.cancelOverlay.bind(OptionsPage));
  2320.     }
  2321.   };
  2322.  
  2323.   /**
  2324.    * Does a bounds check for the element on the given x, y client coordinates.
  2325.    * @param {Element} e The DOM element.
  2326.    * @param {number} x The client X to check.
  2327.    * @param {number} y The client Y to check.
  2328.    * @return {boolean} True if the point falls within the element's bounds.
  2329.    * @private
  2330.    */
  2331.   OptionsPage.elementContainsPoint_ = function(e, x, y) {
  2332.     var clientRect = e.getBoundingClientRect();
  2333.     return x >= clientRect.left && x <= clientRect.right &&
  2334.         y >= clientRect.top && y <= clientRect.bottom;
  2335.   };
  2336.  
  2337.   /**
  2338.    * Called when the page is scrolled; moves elements that are position:fixed
  2339.    * but should only behave as if they are fixed for vertical scrolling.
  2340.    * @private
  2341.    */
  2342.   OptionsPage.handleScroll_ = function() {
  2343.     this.updateAllFrozenElementPositions_();
  2344.   };
  2345.  
  2346.   /**
  2347.    * Updates all frozen pages to match the horizontal scroll position.
  2348.    * @private
  2349.    */
  2350.   OptionsPage.updateAllFrozenElementPositions_ = function() {
  2351.     var frozenElements = document.querySelectorAll('.frozen');
  2352.     for (var i = 0; i < frozenElements.length; i++)
  2353.       this.updateFrozenElementHorizontalPosition_(frozenElements[i]);
  2354.   };
  2355.  
  2356.   /**
  2357.    * Updates the given frozen element to match the horizontal scroll position.
  2358.    * @param {HTMLElement} e The frozen element to update.
  2359.    * @private
  2360.    */
  2361.   OptionsPage.updateFrozenElementHorizontalPosition_ = function(e) {
  2362.     if (isRTL()) {
  2363.       e.style.right = OptionsPage.horizontalOffset + 'px';
  2364.     } else {
  2365.       e.style.left = OptionsPage.horizontalOffset -
  2366.           document.body.scrollLeft + 'px';
  2367.     }
  2368.   };
  2369.  
  2370.   /**
  2371.    * Change the horizontal offset used to reposition elements while showing an
  2372.    * overlay from the default.
  2373.    */
  2374.   OptionsPage.setHorizontalOffset = function(value) {
  2375.     OptionsPage.horizontalOffset = value;
  2376.   };
  2377.  
  2378.   OptionsPage.setClearPluginLSODataEnabled = function(enabled) {
  2379.     if (enabled) {
  2380.       document.documentElement.setAttribute(
  2381.           'flashPluginSupportsClearSiteData', '');
  2382.     } else {
  2383.       document.documentElement.removeAttribute(
  2384.           'flashPluginSupportsClearSiteData');
  2385.     }
  2386.   };
  2387.  
  2388.   OptionsPage.setPepperFlashSettingsEnabled = function(enabled) {
  2389.     if (enabled) {
  2390.       document.documentElement.setAttribute(
  2391.           'enablePepperFlashSettings', '');
  2392.     } else {
  2393.       document.documentElement.removeAttribute(
  2394.           'enablePepperFlashSettings');
  2395.     }
  2396.   };
  2397.  
  2398.   OptionsPage.prototype = {
  2399.     __proto__: cr.EventTarget.prototype,
  2400.  
  2401.     /**
  2402.      * The parent page of this option page, or null for top-level pages.
  2403.      * @type {OptionsPage}
  2404.      */
  2405.     parentPage: null,
  2406.  
  2407.     /**
  2408.      * The section on the parent page that is associated with this page.
  2409.      * Can be null.
  2410.      * @type {Element}
  2411.      */
  2412.     associatedSection: null,
  2413.  
  2414.     /**
  2415.      * An array of controls that are associated with this page.  The first
  2416.      * control should be located on a top-level page.
  2417.      * @type {OptionsPage}
  2418.      */
  2419.     associatedControls: null,
  2420.  
  2421.     /**
  2422.      * Initializes page content.
  2423.      */
  2424.     initializePage: function() {},
  2425.  
  2426.     /**
  2427.      * Sets focus on the first focusable element. Override for a custom focus
  2428.      * strategy.
  2429.      */
  2430.     focus: function() {
  2431.       var elements = this.pageDiv.querySelectorAll(
  2432.           'input, list, select, textarea, button');
  2433.       for (var i = 0; i < elements.length; i++) {
  2434.         var element = elements[i];
  2435.         // Try to focus. If fails, then continue.
  2436.         element.focus();
  2437.         if (document.activeElement == element)
  2438.           return;
  2439.       }
  2440.     },
  2441.  
  2442.     /**
  2443.      * Gets the container div for this page if it is an overlay.
  2444.      * @type {HTMLElement}
  2445.      */
  2446.     get container() {
  2447.       assert(this.isOverlay);
  2448.       return this.pageDiv.parentNode;
  2449.     },
  2450.  
  2451.     /**
  2452.      * Gets page visibility state.
  2453.      * @type {boolean}
  2454.      */
  2455.     get visible() {
  2456.       // If this is an overlay dialog it is no longer considered visible while
  2457.       // the overlay is fading out. See http://crbug.com/118629.
  2458.       if (this.isOverlay &&
  2459.           this.container.classList.contains('transparent')) {
  2460.         return false;
  2461.       }
  2462.       return !this.pageDiv.hidden;
  2463.     },
  2464.  
  2465.     /**
  2466.      * Sets page visibility.
  2467.      * @type {boolean}
  2468.      */
  2469.     set visible(visible) {
  2470.       if ((this.visible && visible) || (!this.visible && !visible))
  2471.         return;
  2472.  
  2473.       // If using an overlay, the visibility of the dialog is toggled at the
  2474.       // same time as the overlay to show the dialog's out transition. This
  2475.       // is handled in setOverlayVisible.
  2476.       if (this.isOverlay) {
  2477.         this.setOverlayVisible_(visible);
  2478.       } else {
  2479.         this.pageDiv.hidden = !visible;
  2480.         this.onVisibilityChanged_();
  2481.       }
  2482.  
  2483.       cr.dispatchPropertyChange(this, 'visible', visible, !visible);
  2484.     },
  2485.  
  2486.     /**
  2487.      * Shows or hides an overlay (including any visible dialog).
  2488.      * @param {boolean} visible Whether the overlay should be visible or not.
  2489.      * @private
  2490.      */
  2491.     setOverlayVisible_: function(visible) {
  2492.       assert(this.isOverlay);
  2493.       var pageDiv = this.pageDiv;
  2494.       var container = this.container;
  2495.  
  2496.       if (visible) {
  2497.         uber.invokeMethodOnParent('beginInterceptingEvents');
  2498.         this.pageDiv.removeAttribute('aria-hidden');
  2499.         if (this.parentPage)
  2500.           this.parentPage.pageDiv.setAttribute('aria-hidden', true);
  2501.       } else {
  2502.         if (this.parentPage)
  2503.           this.parentPage.pageDiv.removeAttribute('aria-hidden');
  2504.       }
  2505.  
  2506.       if (container.hidden != visible) {
  2507.         if (visible) {
  2508.           // If the container is set hidden and then immediately set visible
  2509.           // again, the fadeCompleted_ callback would cause it to be erroneously
  2510.           // hidden again. Removing the transparent tag avoids that.
  2511.           container.classList.remove('transparent');
  2512.  
  2513.           // Hide all dialogs in this container since a different one may have
  2514.           // been previously visible before fading out.
  2515.           var pages = container.querySelectorAll('.page');
  2516.           for (var i = 0; i < pages.length; i++)
  2517.             pages[i].hidden = true;
  2518.           // Show the new dialog.
  2519.           pageDiv.hidden = false;
  2520.         }
  2521.         return;
  2522.       }
  2523.  
  2524.       if (visible) {
  2525.         container.hidden = false;
  2526.         pageDiv.hidden = false;
  2527.         // NOTE: This is a hacky way to force the container to layout which
  2528.         // will allow us to trigger the webkit transition.
  2529.         container.scrollTop;
  2530.         container.classList.remove('transparent');
  2531.         this.onVisibilityChanged_();
  2532.       } else {
  2533.         var self = this;
  2534.         // TODO: Use an event delegate to avoid having to subscribe and
  2535.         // unsubscribe for webkitTransitionEnd events.
  2536.         container.addEventListener('webkitTransitionEnd', function f(e) {
  2537.           if (e.target != e.currentTarget || e.propertyName != 'opacity')
  2538.             return;
  2539.           container.removeEventListener('webkitTransitionEnd', f);
  2540.           self.fadeCompleted_();
  2541.         });
  2542.         container.classList.add('transparent');
  2543.       }
  2544.     },
  2545.  
  2546.     /**
  2547.      * Called when a container opacity transition finishes.
  2548.      * @private
  2549.      */
  2550.     fadeCompleted_: function() {
  2551.       if (this.container.classList.contains('transparent')) {
  2552.         this.pageDiv.hidden = true;
  2553.         this.container.hidden = true;
  2554.         this.onVisibilityChanged_();
  2555.         if (this.nestingLevel == 1)
  2556.           uber.invokeMethodOnParent('stopInterceptingEvents');
  2557.       }
  2558.     },
  2559.  
  2560.     /**
  2561.      * Called when a page is shown or hidden to update the root options page
  2562.      * based on this page's visibility.
  2563.      * @private
  2564.      */
  2565.     onVisibilityChanged_: function() {
  2566.       OptionsPage.updateRootPageFreezeState();
  2567.  
  2568.       if (this.isOverlay && !this.visible)
  2569.         OptionsPage.updateScrollPosition_();
  2570.     },
  2571.  
  2572.     /**
  2573.      * The nesting level of this page.
  2574.      * @type {number} The nesting level of this page (0 for top-level page)
  2575.      */
  2576.     get nestingLevel() {
  2577.       var level = 0;
  2578.       var parent = this.parentPage;
  2579.       while (parent) {
  2580.         level++;
  2581.         parent = parent.parentPage;
  2582.       }
  2583.       return level;
  2584.     },
  2585.  
  2586.     /**
  2587.      * Whether the page is considered 'sticky', such that it will
  2588.      * remain a top-level page even if sub-pages change.
  2589.      * @type {boolean} True if this page is sticky.
  2590.      */
  2591.     get sticky() {
  2592.       return false;
  2593.     },
  2594.  
  2595.     /**
  2596.      * Checks whether this page is an ancestor of the given page in terms of
  2597.      * subpage nesting.
  2598.      * @param {OptionsPage} page The potential descendent of this page.
  2599.      * @return {boolean} True if |page| is nested under this page.
  2600.      */
  2601.     isAncestorOfPage: function(page) {
  2602.       var parent = page.parentPage;
  2603.       while (parent) {
  2604.         if (parent == this)
  2605.           return true;
  2606.         parent = parent.parentPage;
  2607.       }
  2608.       return false;
  2609.     },
  2610.  
  2611.     /**
  2612.      * Whether it should be possible to show the page.
  2613.      * @return {boolean} True if the page should be shown.
  2614.      */
  2615.     canShowPage: function() {
  2616.       return true;
  2617.     },
  2618.   };
  2619.  
  2620.   // Export
  2621.   return {
  2622.     OptionsPage: OptionsPage
  2623.   };
  2624. });
  2625.  
  2626. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2627. // Use of this source code is governed by a BSD-style license that can be
  2628. // found in the LICENSE file.
  2629.  
  2630. cr.define('options', function() {
  2631.  
  2632.   var Preferences = options.Preferences;
  2633.  
  2634.   /**
  2635.    * Allows an element to be disabled for several reasons.
  2636.    * The element is disabled if at least one reason is true, and the reasons
  2637.    * can be set separately.
  2638.    * @private
  2639.    * @param {!HTMLElement} el The element to update.
  2640.    * @param {string} reason The reason for disabling the element.
  2641.    * @param {boolean} disabled Whether the element should be disabled or enabled
  2642.    * for the given |reason|.
  2643.    */
  2644.   function updateDisabledState_(el, reason, disabled) {
  2645.     if (!el.disabledReasons)
  2646.       el.disabledReasons = {};
  2647.     if (el.disabled && (Object.keys(el.disabledReasons).length == 0)) {
  2648.       // The element has been previously disabled without a reason, so we add
  2649.       // one to keep it disabled.
  2650.       el.disabledReasons.other = true;
  2651.     }
  2652.     if (!el.disabled) {
  2653.       // If the element is not disabled, there should be no reason, except for
  2654.       // 'other'.
  2655.       delete el.disabledReasons.other;
  2656.       if (Object.keys(el.disabledReasons).length > 0)
  2657.         console.error('Element is not disabled but should be');
  2658.     }
  2659.     if (disabled) {
  2660.       el.disabledReasons[reason] = true;
  2661.     } else {
  2662.       delete el.disabledReasons[reason];
  2663.     }
  2664.     el.disabled = Object.keys(el.disabledReasons).length > 0;
  2665.   }
  2666.  
  2667.   /////////////////////////////////////////////////////////////////////////////
  2668.   // PrefInputElement class:
  2669.  
  2670.   // Define a constructor that uses an input element as its underlying element.
  2671.   var PrefInputElement = cr.ui.define('input');
  2672.  
  2673.   PrefInputElement.prototype = {
  2674.     // Set up the prototype chain
  2675.     __proto__: HTMLInputElement.prototype,
  2676.  
  2677.     /**
  2678.      * Initialization function for the cr.ui framework.
  2679.      */
  2680.     decorate: function() {
  2681.       var self = this;
  2682.  
  2683.       // Listen for user events.
  2684.       this.addEventListener('change', this.handleChange_.bind(this));
  2685.  
  2686.       // Listen for pref changes.
  2687.       Preferences.getInstance().addEventListener(this.pref, function(event) {
  2688.         if (event.value.uncommitted && !self.dialogPref)
  2689.           return;
  2690.         self.updateStateFromPref_(event);
  2691.         updateDisabledState_(self, 'notUserModifiable', event.value.disabled);
  2692.         self.controlledBy = event.value.controlledBy;
  2693.       });
  2694.     },
  2695.  
  2696.     /**
  2697.      * Handle changes to the input element's state made by the user. If a custom
  2698.      * change handler does not suppress it, a default handler is invoked that
  2699.      * updates the associated pref.
  2700.      * @param {Event} event Change event.
  2701.      * @private
  2702.      */
  2703.     handleChange_: function(event) {
  2704.       if (!this.customChangeHandler(event))
  2705.         this.updatePrefFromState_();
  2706.     },
  2707.  
  2708.     /**
  2709.      * Update the input element's state when the associated pref changes.
  2710.      * @param {Event} event Pref change event.
  2711.      * @private
  2712.      */
  2713.     updateStateFromPref_: function(event) {
  2714.       this.value = event.value.value;
  2715.     },
  2716.  
  2717.     /**
  2718.      * See |updateDisabledState_| above.
  2719.      */
  2720.     setDisabled: function(reason, disabled) {
  2721.       updateDisabledState_(this, reason, disabled);
  2722.     },
  2723.  
  2724.     /**
  2725.      * Custom change handler that is invoked first when the user makes changes
  2726.      * to the input element's state. If it returns false, a default handler is
  2727.      * invoked next that updates the associated pref. If it returns true, the
  2728.      * default handler is suppressed (i.e., this works like stopPropagation or
  2729.      * cancelBubble).
  2730.      * @param {Event} event Input element change event.
  2731.      */
  2732.     customChangeHandler: function(event) {
  2733.       return false;
  2734.     },
  2735.   };
  2736.  
  2737.   /**
  2738.    * The name of the associated preference.
  2739.    * @type {string}
  2740.    */
  2741.   cr.defineProperty(PrefInputElement, 'pref', cr.PropertyKind.ATTR);
  2742.  
  2743.   /**
  2744.    * The data type of the associated preference, only relevant for derived
  2745.    * classes that support different data types.
  2746.    * @type {string}
  2747.    */
  2748.   cr.defineProperty(PrefInputElement, 'dataType', cr.PropertyKind.ATTR);
  2749.  
  2750.   /**
  2751.    * Whether this input element is part of a dialog. If so, changes take effect
  2752.    * in the settings UI immediately but are only actually committed when the
  2753.    * user confirms the dialog. If the user cancels the dialog instead, the
  2754.    * changes are rolled back in the settings UI and never committed.
  2755.    * @type {boolean}
  2756.    */
  2757.   cr.defineProperty(PrefInputElement, 'dialogPref', cr.PropertyKind.BOOL_ATTR);
  2758.  
  2759.   /**
  2760.    * Whether the associated preference is controlled by a source other than the
  2761.    * user's setting (can be 'policy', 'extension', 'recommended' or unset).
  2762.    * @type {string}
  2763.    */
  2764.   cr.defineProperty(PrefInputElement, 'controlledBy', cr.PropertyKind.ATTR);
  2765.  
  2766.   /**
  2767.    * The user metric string.
  2768.    * @type {string}
  2769.    */
  2770.   cr.defineProperty(PrefInputElement, 'metric', cr.PropertyKind.ATTR);
  2771.  
  2772.   /////////////////////////////////////////////////////////////////////////////
  2773.   // PrefCheckbox class:
  2774.  
  2775.   // Define a constructor that uses an input element as its underlying element.
  2776.   var PrefCheckbox = cr.ui.define('input');
  2777.  
  2778.   PrefCheckbox.prototype = {
  2779.     // Set up the prototype chain
  2780.     __proto__: PrefInputElement.prototype,
  2781.  
  2782.     /**
  2783.      * Initialization function for the cr.ui framework.
  2784.      */
  2785.     decorate: function() {
  2786.       PrefInputElement.prototype.decorate.call(this);
  2787.       this.type = 'checkbox';
  2788.     },
  2789.  
  2790.     /**
  2791.      * Update the associated pref when when the user makes changes to the
  2792.      * checkbox state.
  2793.      * @private
  2794.      */
  2795.     updatePrefFromState_: function() {
  2796.       var value = this.inverted_pref ? !this.checked : this.checked;
  2797.       Preferences.setBooleanPref(this.pref, value,
  2798.                                  !this.dialogPref, this.metric);
  2799.     },
  2800.  
  2801.     /**
  2802.      * Update the checkbox state when the associated pref changes.
  2803.      * @param {Event} event Pref change event.
  2804.      * @private
  2805.      */
  2806.     updateStateFromPref_: function(event) {
  2807.       var value = Boolean(event.value.value);
  2808.       this.checked = this.inverted_pref ? !value : value;
  2809.     },
  2810.   };
  2811.  
  2812.   /**
  2813.    * Whether the mapping between checkbox state and associated pref is inverted.
  2814.    * @type {boolean}
  2815.    */
  2816.   cr.defineProperty(PrefCheckbox, 'inverted_pref', cr.PropertyKind.BOOL_ATTR);
  2817.  
  2818.   /////////////////////////////////////////////////////////////////////////////
  2819.   // PrefNumber class:
  2820.  
  2821.   // Define a constructor that uses an input element as its underlying element.
  2822.   var PrefNumber = cr.ui.define('input');
  2823.  
  2824.   PrefNumber.prototype = {
  2825.     // Set up the prototype chain
  2826.     __proto__: PrefInputElement.prototype,
  2827.  
  2828.     /**
  2829.      * Initialization function for the cr.ui framework.
  2830.      */
  2831.     decorate: function() {
  2832.       PrefInputElement.prototype.decorate.call(this);
  2833.       this.type = 'number';
  2834.     },
  2835.  
  2836.     /**
  2837.      * Update the associated pref when when the user inputs a number.
  2838.      * @private
  2839.      */
  2840.     updatePrefFromState_: function() {
  2841.       if (this.validity.valid) {
  2842.         Preferences.setIntegerPref(this.pref, this.value,
  2843.                                    !this.dialogPref, this.metric);
  2844.       }
  2845.     },
  2846.   };
  2847.  
  2848.   /////////////////////////////////////////////////////////////////////////////
  2849.   // PrefRadio class:
  2850.  
  2851.   //Define a constructor that uses an input element as its underlying element.
  2852.   var PrefRadio = cr.ui.define('input');
  2853.  
  2854.   PrefRadio.prototype = {
  2855.     // Set up the prototype chain
  2856.     __proto__: PrefInputElement.prototype,
  2857.  
  2858.     /**
  2859.      * Initialization function for the cr.ui framework.
  2860.      */
  2861.     decorate: function() {
  2862.       PrefInputElement.prototype.decorate.call(this);
  2863.       this.type = 'radio';
  2864.     },
  2865.  
  2866.     /**
  2867.      * Update the associated pref when when the user selects the radio button.
  2868.      * @private
  2869.      */
  2870.     updatePrefFromState_: function() {
  2871.       if (this.value == 'true' || this.value == 'false') {
  2872.         Preferences.setBooleanPref(this.pref,
  2873.                                    this.value == String(this.checked),
  2874.                                    !this.dialogPref, this.metric);
  2875.       } else {
  2876.         Preferences.setIntegerPref(this.pref, this.value,
  2877.                                    !this.dialogPref, this.metric);
  2878.       }
  2879.     },
  2880.  
  2881.     /**
  2882.      * Update the radio button state when the associated pref changes.
  2883.      * @param {Event} event Pref change event.
  2884.      * @private
  2885.      */
  2886.     updateStateFromPref_: function(event) {
  2887.       this.checked = this.value == String(event.value.value);
  2888.     },
  2889.   };
  2890.  
  2891.   /////////////////////////////////////////////////////////////////////////////
  2892.   // PrefRange class:
  2893.  
  2894.   // Define a constructor that uses an input element as its underlying element.
  2895.   var PrefRange = cr.ui.define('input');
  2896.  
  2897.   PrefRange.prototype = {
  2898.     // Set up the prototype chain
  2899.     __proto__: PrefInputElement.prototype,
  2900.  
  2901.     /**
  2902.      * The map from slider position to corresponding pref value.
  2903.      */
  2904.     valueMap: undefined,
  2905.  
  2906.     /**
  2907.      * Initialization function for the cr.ui framework.
  2908.      */
  2909.     decorate: function() {
  2910.       PrefInputElement.prototype.decorate.call(this);
  2911.       this.type = 'range';
  2912.  
  2913.       // Listen for user events.
  2914.       // TODO(jhawkins): Add onmousewheel handling once the associated WK bug is
  2915.       // fixed.
  2916.       // https://bugs.webkit.org/show_bug.cgi?id=52256
  2917.       this.addEventListener('keyup', this.handleRelease_.bind(this));
  2918.       this.addEventListener('mouseup', this.handleRelease_.bind(this));
  2919.     },
  2920.  
  2921.     /**
  2922.      * Update the associated pref when when the user releases the slider.
  2923.      * @private
  2924.      */
  2925.     updatePrefFromState_: function() {
  2926.       Preferences.setIntegerPref(this.pref, this.mapPositionToPref(this.value),
  2927.                                  !this.dialogPref, this.metric);
  2928.     },
  2929.  
  2930.     /**
  2931.      * Ignore changes to the slider position made by the user while the slider
  2932.      * has not been released.
  2933.      * @private
  2934.      */
  2935.     handleChange_: function() {
  2936.     },
  2937.  
  2938.     /**
  2939.      * Handle changes to the slider position made by the user when the slider is
  2940.      * released. If a custom change handler does not suppress it, a default
  2941.      * handler is invoked that updates the associated pref.
  2942.      * @param {Event} event Change event.
  2943.      * @private
  2944.      */
  2945.     handleRelease_: function(event) {
  2946.       if (!this.customChangeHandler(event))
  2947.         this.updatePrefFromState_();
  2948.     },
  2949.  
  2950.     /**
  2951.      * Update the slider position when the associated pref changes.
  2952.      * @param {Event} event Pref change event.
  2953.      * @private
  2954.      */
  2955.     updateStateFromPref_: function(event) {
  2956.       var value = event.value.value;
  2957.       this.value = this.valueMap ? this.valueMap.indexOf(value) : value;
  2958.     },
  2959.  
  2960.     /**
  2961.      * Map slider position to the range of values provided by the client,
  2962.      * represented by |valueMap|.
  2963.      * @param {number} position The slider position to map.
  2964.      */
  2965.     mapPositionToPref: function(position) {
  2966.       return this.valueMap ? this.valueMap[position] : position;
  2967.     },
  2968.   };
  2969.  
  2970.   /////////////////////////////////////////////////////////////////////////////
  2971.   // PrefSelect class:
  2972.  
  2973.   // Define a constructor that uses a select element as its underlying element.
  2974.   var PrefSelect = cr.ui.define('select');
  2975.  
  2976.   PrefSelect.prototype = {
  2977.     // Set up the prototype chain
  2978.     __proto__: PrefInputElement.prototype,
  2979.  
  2980.     /**
  2981.      * Update the associated pref when when the user selects an item.
  2982.      * @private
  2983.      */
  2984.     updatePrefFromState_: function() {
  2985.       var value = this.options[this.selectedIndex].value;
  2986.       switch (this.dataType) {
  2987.         case 'number':
  2988.           Preferences.setIntegerPref(this.pref, value,
  2989.                                      !this.dialogPref, this.metric);
  2990.           break;
  2991.         case 'double':
  2992.           Preferences.setDoublePref(this.pref, value,
  2993.                                     !this.dialogPref, this.metric);
  2994.           break;
  2995.         case 'boolean':
  2996.           Preferences.setBooleanPref(this.pref, value == 'true',
  2997.                                      !this.dialogPref, this.metric);
  2998.           break;
  2999.         case 'string':
  3000.           Preferences.setStringPref(this.pref, value,
  3001.                                     !this.dialogPref, this.metric);
  3002.           break;
  3003.         default:
  3004.           console.error('Unknown data type for <select> UI element: ' +
  3005.                         this.dataType);
  3006.       }
  3007.     },
  3008.  
  3009.     /**
  3010.      * Update the selected item when the associated pref changes.
  3011.      * @param {Event} event Pref change event.
  3012.      * @private
  3013.      */
  3014.     updateStateFromPref_: function(event) {
  3015.       // Make sure the value is a string, because the value is stored as a
  3016.       // string in the HTMLOptionElement.
  3017.       value = String(event.value.value);
  3018.  
  3019.       var found = false;
  3020.       for (var i = 0; i < this.options.length; i++) {
  3021.         if (this.options[i].value == value) {
  3022.           this.selectedIndex = i;
  3023.           found = true;
  3024.         }
  3025.       }
  3026.  
  3027.       // Item not found, select first item.
  3028.       if (!found)
  3029.         this.selectedIndex = 0;
  3030.  
  3031.       // The "onchange" event automatically fires when the user makes a manual
  3032.       // change. It should never be fired for a programmatic change. However,
  3033.       // these two lines were here already and it is hard to tell who may be
  3034.       // relying on them.
  3035.       if (this.onchange)
  3036.         this.onchange(event);
  3037.     },
  3038.   };
  3039.  
  3040.   /////////////////////////////////////////////////////////////////////////////
  3041.   // PrefTextField class:
  3042.  
  3043.   // Define a constructor that uses an input element as its underlying element.
  3044.   var PrefTextField = cr.ui.define('input');
  3045.  
  3046.   PrefTextField.prototype = {
  3047.     // Set up the prototype chain
  3048.     __proto__: PrefInputElement.prototype,
  3049.  
  3050.     /**
  3051.      * Initialization function for the cr.ui framework.
  3052.      */
  3053.     decorate: function() {
  3054.       PrefInputElement.prototype.decorate.call(this);
  3055.       var self = this;
  3056.  
  3057.       // Listen for user events.
  3058.       window.addEventListener('unload', function() {
  3059.         if (document.activeElement == self)
  3060.           self.blur();
  3061.       });
  3062.     },
  3063.  
  3064.     /**
  3065.      * Update the associated pref when when the user inputs text.
  3066.      * @private
  3067.      */
  3068.     updatePrefFromState_: function(event) {
  3069.       switch (this.dataType) {
  3070.         case 'number':
  3071.           Preferences.setIntegerPref(this.pref, this.value,
  3072.                                      !this.dialogPref, this.metric);
  3073.           break;
  3074.         case 'double':
  3075.           Preferences.setDoublePref(this.pref, this.value,
  3076.                                     !this.dialogPref, this.metric);
  3077.           break;
  3078.         case 'url':
  3079.           Preferences.setURLPref(this.pref, this.value,
  3080.                                  !this.dialogPref, this.metric);
  3081.           break;
  3082.         default:
  3083.           Preferences.setStringPref(this.pref, this.value,
  3084.                                     !this.dialogPref, this.metric);
  3085.           break;
  3086.       }
  3087.     },
  3088.   };
  3089.  
  3090.   /////////////////////////////////////////////////////////////////////////////
  3091.   // PrefButton class:
  3092.  
  3093.   // Define a constructor that uses a button element as its underlying element.
  3094.   var PrefButton = cr.ui.define('button');
  3095.  
  3096.   PrefButton.prototype = {
  3097.     // Set up the prototype chain
  3098.     __proto__: HTMLButtonElement.prototype,
  3099.  
  3100.     /**
  3101.      * Initialization function for the cr.ui framework.
  3102.      */
  3103.     decorate: function() {
  3104.       var self = this;
  3105.  
  3106.       // Listen for pref changes.
  3107.       // This element behaves like a normal button and does not affect the
  3108.       // underlying preference; it just becomes disabled when the preference is
  3109.       // managed, and its value is false. This is useful for buttons that should
  3110.       // be disabled when the underlying Boolean preference is set to false by a
  3111.       // policy or extension.
  3112.       Preferences.getInstance().addEventListener(this.pref, function(event) {
  3113.         updateDisabledState_(self, 'notUserModifiable',
  3114.                              event.value.disabled && !event.value.value);
  3115.         self.controlledBy = event.value.controlledBy;
  3116.       });
  3117.     },
  3118.  
  3119.     /**
  3120.      * See |updateDisabledState_| above.
  3121.      */
  3122.     setDisabled: function(reason, disabled) {
  3123.       updateDisabledState_(this, reason, disabled);
  3124.     },
  3125.   };
  3126.  
  3127.   /**
  3128.    * The name of the associated preference.
  3129.    * @type {string}
  3130.    */
  3131.   cr.defineProperty(PrefButton, 'pref', cr.PropertyKind.ATTR);
  3132.  
  3133.   /**
  3134.    * Whether the associated preference is controlled by a source other than the
  3135.    * user's setting (can be 'policy', 'extension', 'recommended' or unset).
  3136.    * @type {string}
  3137.    */
  3138.   cr.defineProperty(PrefButton, 'controlledBy', cr.PropertyKind.ATTR);
  3139.  
  3140.   // Export
  3141.   return {
  3142.     PrefCheckbox: PrefCheckbox,
  3143.     PrefNumber: PrefNumber,
  3144.     PrefRadio: PrefRadio,
  3145.     PrefRange: PrefRange,
  3146.     PrefSelect: PrefSelect,
  3147.     PrefTextField: PrefTextField,
  3148.     PrefButton: PrefButton
  3149.   };
  3150.  
  3151. });
  3152.  
  3153. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3154. // Use of this source code is governed by a BSD-style license that can be
  3155. // found in the LICENSE file.
  3156.  
  3157. /**
  3158.  * @fileoverview Base class for dialogs that require saving preferences on
  3159.  *     confirm and resetting preference inputs on cancel.
  3160.  */
  3161.  
  3162. cr.define('options', function() {
  3163.   /** @const */ var OptionsPage = options.OptionsPage;
  3164.  
  3165.   /**
  3166.    * Base class for settings dialogs.
  3167.    * @constructor
  3168.    * @param {string} name See OptionsPage constructor.
  3169.    * @param {string} title See OptionsPage constructor.
  3170.    * @param {string} pageDivName See OptionsPage constructor.
  3171.    * @param {HTMLInputElement} okButton The confirmation button element.
  3172.    * @param {HTMLInputElement} cancelButton The cancellation button element.
  3173.    * @extends {OptionsPage}
  3174.    */
  3175.   function SettingsDialog(name, title, pageDivName, okButton, cancelButton) {
  3176.     OptionsPage.call(this, name, title, pageDivName);
  3177.     this.okButton = okButton;
  3178.     this.cancelButton = cancelButton;
  3179.   }
  3180.  
  3181.   SettingsDialog.prototype = {
  3182.     __proto__: OptionsPage.prototype,
  3183.  
  3184.     /** @override */
  3185.     initializePage: function() {
  3186.       this.okButton.onclick = this.handleConfirm.bind(this);
  3187.       this.cancelButton.onclick = this.handleCancel.bind(this);
  3188.     },
  3189.  
  3190.     /**
  3191.      * Handles the confirm button by saving the dialog preferences.
  3192.      */
  3193.     handleConfirm: function() {
  3194.       OptionsPage.closeOverlay();
  3195.  
  3196.       var prefs = Preferences.getInstance();
  3197.       var els = this.pageDiv.querySelectorAll('[dialog-pref]');
  3198.       for (var i = 0; i < els.length; i++) {
  3199.         if (els[i].pref)
  3200.           prefs.commitPref(els[i].pref, els[i].metric);
  3201.       }
  3202.     },
  3203.  
  3204.     /**
  3205.      * Handles the cancel button by closing the overlay.
  3206.      */
  3207.     handleCancel: function() {
  3208.       OptionsPage.closeOverlay();
  3209.  
  3210.       var prefs = Preferences.getInstance();
  3211.       var els = this.pageDiv.querySelectorAll('[dialog-pref]');
  3212.       for (var i = 0; i < els.length; i++) {
  3213.         if (els[i].pref)
  3214.           prefs.rollbackPref(els[i].pref);
  3215.       }
  3216.     },
  3217.   };
  3218.  
  3219.   return {
  3220.     SettingsDialog: SettingsDialog
  3221.   };
  3222. });
  3223.  
  3224.  
  3225.  
  3226. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3227. // Use of this source code is governed by a BSD-style license that can be
  3228. // found in the LICENSE file.
  3229.  
  3230. cr.define('options', function() {
  3231.   var OptionsPage = options.OptionsPage;
  3232.  
  3233.   /**
  3234.    * AlertOverlay class
  3235.    * Encapsulated handling of a generic alert.
  3236.    * @class
  3237.    */
  3238.   function AlertOverlay() {
  3239.     OptionsPage.call(this, 'alertOverlay', '', 'alertOverlay');
  3240.   }
  3241.  
  3242.   cr.addSingletonGetter(AlertOverlay);
  3243.  
  3244.   AlertOverlay.prototype = {
  3245.     // Inherit AlertOverlay from OptionsPage.
  3246.     __proto__: OptionsPage.prototype,
  3247.  
  3248.     /**
  3249.      * Whether the page can be shown. Used to make sure the page is only
  3250.      * shown via AlertOverlay.Show(), and not via the address bar.
  3251.      * @private
  3252.      */
  3253.     canShow_: false,
  3254.  
  3255.     /**
  3256.      * Initialize the page.
  3257.      */
  3258.     initializePage: function() {
  3259.       // Call base class implementation to start preference initialization.
  3260.       OptionsPage.prototype.initializePage.call(this);
  3261.  
  3262.       var self = this;
  3263.       $('alertOverlayOk').onclick = function(event) {
  3264.         self.handleOK_();
  3265.       };
  3266.  
  3267.       $('alertOverlayCancel').onclick = function(event) {
  3268.         self.handleCancel_();
  3269.       };
  3270.     },
  3271.  
  3272.     /** @override */
  3273.     get nestingLevel() {
  3274.       // AlertOverlay is special in that it is not tied to one page or overlay.
  3275.       // Set the nesting level arbitrarily high so as to always be recognized as
  3276.       // the top-most visible page.
  3277.       return 99;
  3278.     },
  3279.  
  3280.     /**
  3281.      * Handle the 'ok' button.  Clear the overlay and call the ok callback if
  3282.      * available.
  3283.      * @private
  3284.      */
  3285.     handleOK_: function() {
  3286.       OptionsPage.closeOverlay();
  3287.       if (this.okCallback != undefined) {
  3288.         this.okCallback.call();
  3289.       }
  3290.     },
  3291.  
  3292.     /**
  3293.      * Handle the 'cancel' button.  Clear the overlay and call the cancel
  3294.      * callback if available.
  3295.      * @private
  3296.      */
  3297.     handleCancel_: function() {
  3298.       OptionsPage.closeOverlay();
  3299.       if (this.cancelCallback != undefined) {
  3300.         this.cancelCallback.call();
  3301.       }
  3302.     },
  3303.  
  3304.     /**
  3305.      * The page is getting hidden. Don't let it be shown again.
  3306.      */
  3307.     willHidePage: function() {
  3308.       canShow_ = false;
  3309.     },
  3310.  
  3311.     /** @override */
  3312.     canShowPage: function() {
  3313.       return this.canShow_;
  3314.     },
  3315.   };
  3316.  
  3317.   /**
  3318.    * Show an alert overlay with the given message, button titles, and
  3319.    * callbacks.
  3320.    * @param {string} title The alert title to display to the user.
  3321.    * @param {string} message The alert message to display to the user.
  3322.    * @param {string} okTitle The title of the OK button. If undefined or empty,
  3323.    *     no button is shown.
  3324.    * @param {string} cancelTitle The title of the cancel button. If undefined or
  3325.    *     empty, no button is shown.
  3326.    * @param {function} okCallback A function to be called when the user presses
  3327.    *     the ok button.  The alert window will be closed automatically.  Can be
  3328.    *     undefined.
  3329.    * @param {function} cancelCallback A function to be called when the user
  3330.    *     presses the cancel button.  The alert window will be closed
  3331.    *     automatically.  Can be undefined.
  3332.    */
  3333.   AlertOverlay.show = function(
  3334.       title, message, okTitle, cancelTitle, okCallback, cancelCallback) {
  3335.     if (title != undefined) {
  3336.       $('alertOverlayTitle').textContent = title;
  3337.       $('alertOverlayTitle').style.display = 'block';
  3338.     } else {
  3339.       $('alertOverlayTitle').style.display = 'none';
  3340.     }
  3341.  
  3342.     if (message != undefined) {
  3343.       $('alertOverlayMessage').textContent = message;
  3344.       $('alertOverlayMessage').style.display = 'block';
  3345.     } else {
  3346.       $('alertOverlayMessage').style.display = 'none';
  3347.     }
  3348.  
  3349.     if (okTitle != undefined && okTitle != '') {
  3350.       $('alertOverlayOk').textContent = okTitle;
  3351.       $('alertOverlayOk').style.display = 'block';
  3352.     } else {
  3353.       $('alertOverlayOk').style.display = 'none';
  3354.     }
  3355.  
  3356.     if (cancelTitle != undefined && cancelTitle != '') {
  3357.       $('alertOverlayCancel').textContent = cancelTitle;
  3358.       $('alertOverlayCancel').style.display = 'inline';
  3359.     } else {
  3360.       $('alertOverlayCancel').style.display = 'none';
  3361.     }
  3362.  
  3363.     var alertOverlay = AlertOverlay.getInstance();
  3364.     alertOverlay.okCallback = okCallback;
  3365.     alertOverlay.cancelCallback = cancelCallback;
  3366.     alertOverlay.canShow_ = true;
  3367.  
  3368.     // Intentionally don't show the URL in the location bar as we don't want
  3369.     // people trying to navigate here by hand.
  3370.     OptionsPage.showPageByName('alertOverlay', false);
  3371.   };
  3372.  
  3373.   // Export
  3374.   return {
  3375.     AlertOverlay: AlertOverlay
  3376.   };
  3377. });
  3378.  
  3379. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3380. // Use of this source code is governed by a BSD-style license that can be
  3381. // found in the LICENSE file.
  3382.  
  3383. cr.define('options', function() {
  3384.   /** @const */ var OptionsPage = options.OptionsPage;
  3385.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  3386.  
  3387.   // The GUID of the loaded address.
  3388.   var guid;
  3389.  
  3390.   /**
  3391.    * AutofillEditAddressOverlay class
  3392.    * Encapsulated handling of the 'Add Page' overlay page.
  3393.    * @class
  3394.    */
  3395.   function AutofillEditAddressOverlay() {
  3396.     OptionsPage.call(this, 'autofillEditAddress',
  3397.                      loadTimeData.getString('autofillEditAddressTitle'),
  3398.                      'autofill-edit-address-overlay');
  3399.   }
  3400.  
  3401.   cr.addSingletonGetter(AutofillEditAddressOverlay);
  3402.  
  3403.   AutofillEditAddressOverlay.prototype = {
  3404.     __proto__: OptionsPage.prototype,
  3405.  
  3406.     /**
  3407.      * Initializes the page.
  3408.      */
  3409.     initializePage: function() {
  3410.       OptionsPage.prototype.initializePage.call(this);
  3411.  
  3412.       this.createMultiValueLists_();
  3413.  
  3414.       var self = this;
  3415.       $('autofill-edit-address-cancel-button').onclick = function(event) {
  3416.         self.dismissOverlay_();
  3417.       };
  3418.  
  3419.       // TODO(jhawkins): Investigate other possible solutions.
  3420.       $('autofill-edit-address-apply-button').onclick = function(event) {
  3421.         // Blur active element to ensure that pending changes are committed.
  3422.         if (document.activeElement)
  3423.           document.activeElement.blur();
  3424.         // Blurring is delayed for list elements.  Queue save and close to
  3425.         // ensure that pending changes have been applied.
  3426.         setTimeout(function() {
  3427.           self.saveAddress_();
  3428.           self.dismissOverlay_();
  3429.         }, 0);
  3430.       };
  3431.  
  3432.       // Prevent 'blur' events on the OK and cancel buttons, which can trigger
  3433.       // insertion of new placeholder elements.  The addition of placeholders
  3434.       // affects layout, which interferes with being able to click on the
  3435.       // buttons.
  3436.       $('autofill-edit-address-apply-button').onmousedown = function(event) {
  3437.         event.preventDefault();
  3438.       };
  3439.       $('autofill-edit-address-cancel-button').onmousedown = function(event) {
  3440.         event.preventDefault();
  3441.       };
  3442.  
  3443.       self.guid = '';
  3444.       self.populateCountryList_();
  3445.       self.clearInputFields_();
  3446.       self.connectInputEvents_();
  3447.     },
  3448.  
  3449.     /**
  3450.      * Creates, decorates and initializes the multi-value lists for full name,
  3451.      * phone, and email.
  3452.      * @private
  3453.      */
  3454.     createMultiValueLists_: function() {
  3455.       var list = $('full-name-list');
  3456.       options.autofillOptions.AutofillNameValuesList.decorate(list);
  3457.       list.autoExpands = true;
  3458.  
  3459.       list = $('phone-list');
  3460.       options.autofillOptions.AutofillPhoneValuesList.decorate(list);
  3461.       list.autoExpands = true;
  3462.  
  3463.       list = $('email-list');
  3464.       options.autofillOptions.AutofillValuesList.decorate(list);
  3465.       list.autoExpands = true;
  3466.     },
  3467.  
  3468.     /**
  3469.      * Updates the data model for the list named |listName| with the values from
  3470.      * |entries|.
  3471.      * @param {String} listName The id of the list.
  3472.      * @param {Array} entries The list of items to be added to the list.
  3473.      */
  3474.     setMultiValueList_: function(listName, entries) {
  3475.       // Add data entries.
  3476.       var list = $(listName);
  3477.  
  3478.       // Add special entry for adding new values.
  3479.       var augmentedList = entries.slice();
  3480.       augmentedList.push(null);
  3481.       list.dataModel = new ArrayDataModel(augmentedList);
  3482.  
  3483.       // Update the status of the 'OK' button.
  3484.       this.inputFieldChanged_();
  3485.  
  3486.       list.dataModel.addEventListener('splice',
  3487.                                       this.inputFieldChanged_.bind(this));
  3488.       list.dataModel.addEventListener('change',
  3489.                                       this.inputFieldChanged_.bind(this));
  3490.     },
  3491.  
  3492.     /**
  3493.      * Clears any uncommitted input, resets the stored GUID and dismisses the
  3494.      * overlay.
  3495.      * @private
  3496.      */
  3497.     dismissOverlay_: function() {
  3498.       this.clearInputFields_();
  3499.       this.guid = '';
  3500.       OptionsPage.closeOverlay();
  3501.     },
  3502.  
  3503.     /**
  3504.      * Aggregates the values in the input fields into an array and sends the
  3505.      * array to the Autofill handler.
  3506.      * @private
  3507.      */
  3508.     saveAddress_: function() {
  3509.       var address = new Array();
  3510.       address[0] = this.guid;
  3511.       var list = $('full-name-list');
  3512.       address[1] = list.dataModel.slice(0, list.dataModel.length - 1);
  3513.       address[2] = $('company-name').value;
  3514.       address[3] = $('addr-line-1').value;
  3515.       address[4] = $('addr-line-2').value;
  3516.       address[5] = $('city').value;
  3517.       address[6] = $('state').value;
  3518.       address[7] = $('postal-code').value;
  3519.       address[8] = $('country').value;
  3520.       list = $('phone-list');
  3521.       address[9] = list.dataModel.slice(0, list.dataModel.length - 1);
  3522.       list = $('email-list');
  3523.       address[10] = list.dataModel.slice(0, list.dataModel.length - 1);
  3524.  
  3525.       chrome.send('setAddress', address);
  3526.     },
  3527.  
  3528.     /**
  3529.      * Connects each input field to the inputFieldChanged_() method that enables
  3530.      * or disables the 'Ok' button based on whether all the fields are empty or
  3531.      * not.
  3532.      * @private
  3533.      */
  3534.     connectInputEvents_: function() {
  3535.       var self = this;
  3536.       $('company-name').oninput = $('addr-line-1').oninput =
  3537.           $('addr-line-2').oninput = $('city').oninput = $('state').oninput =
  3538.           $('postal-code').oninput = function(event) {
  3539.         self.inputFieldChanged_();
  3540.       };
  3541.  
  3542.       $('country').onchange = function(event) {
  3543.         self.countryChanged_();
  3544.       };
  3545.     },
  3546.  
  3547.     /**
  3548.      * Checks the values of each of the input fields and disables the 'Ok'
  3549.      * button if all of the fields are empty.
  3550.      * @private
  3551.      */
  3552.     inputFieldChanged_: function() {
  3553.       // Length of lists are tested for <= 1 due to the "add" placeholder item
  3554.       // in the list.
  3555.       var disabled =
  3556.           $('full-name-list').items.length <= 1 &&
  3557.           !$('company-name').value &&
  3558.           !$('addr-line-1').value && !$('addr-line-2').value &&
  3559.           !$('city').value && !$('state').value && !$('postal-code').value &&
  3560.           !$('country').value && $('phone-list').items.length <= 1 &&
  3561.           $('email-list').items.length <= 1;
  3562.       $('autofill-edit-address-apply-button').disabled = disabled;
  3563.     },
  3564.  
  3565.     /**
  3566.      * Updates the postal code and state field labels appropriately for the
  3567.      * selected country.
  3568.      * @private
  3569.      */
  3570.     countryChanged_: function() {
  3571.       var countryCode = $('country').value ||
  3572.           loadTimeData.getString('defaultCountryCode');
  3573.  
  3574.       var details = loadTimeData.getValue('autofillCountryData')[countryCode];
  3575.       var postal = $('postal-code-label');
  3576.       postal.textContent = details.postalCodeLabel;
  3577.       $('state-label').textContent = details.stateLabel;
  3578.  
  3579.       // Also update the 'Ok' button as needed.
  3580.       this.inputFieldChanged_();
  3581.     },
  3582.  
  3583.     /**
  3584.      * Populates the country <select> list.
  3585.      * @private
  3586.      */
  3587.     populateCountryList_: function() {
  3588.       var countryData = loadTimeData.getValue('autofillCountryData');
  3589.       var defaultCountryCode = loadTimeData.getString('defaultCountryCode');
  3590.  
  3591.       // Build an array of the country names and their corresponding country
  3592.       // codes, so that we can sort and insert them in order.
  3593.       var countries = [];
  3594.       for (var countryCode in countryData) {
  3595.         var country = {
  3596.           countryCode: countryCode,
  3597.           name: countryData[countryCode].name
  3598.         };
  3599.         countries.push(country);
  3600.       }
  3601.  
  3602.       // Sort the countries in alphabetical order by name.
  3603.       countries = countries.sort(function(a, b) {
  3604.         return a.name < b.name ? -1 : 1;
  3605.       });
  3606.  
  3607.       // Insert the empty and default countries at the beginning of the array.
  3608.       var emptyCountry = {
  3609.         countryCode: '',
  3610.         name: ''
  3611.       };
  3612.       var defaultCountry = {
  3613.         countryCode: defaultCountryCode,
  3614.         name: countryData[defaultCountryCode].name
  3615.       };
  3616.       var separator = {
  3617.         countryCode: '',
  3618.         name: '---',
  3619.         disabled: true
  3620.       };
  3621.       countries.unshift(emptyCountry, defaultCountry, separator);
  3622.  
  3623.       // Add the countries to the country <select> list.
  3624.       var countryList = $('country');
  3625.       for (var i = 0; i < countries.length; i++) {
  3626.         var country = new Option(countries[i].name, countries[i].countryCode);
  3627.         country.disabled = countries[i].disabled;
  3628.         countryList.appendChild(country);
  3629.       }
  3630.     },
  3631.  
  3632.     /**
  3633.      * Clears the value of each input field.
  3634.      * @private
  3635.      */
  3636.     clearInputFields_: function() {
  3637.       this.setMultiValueList_('full-name-list', []);
  3638.       $('company-name').value = '';
  3639.       $('addr-line-1').value = '';
  3640.       $('addr-line-2').value = '';
  3641.       $('city').value = '';
  3642.       $('state').value = '';
  3643.       $('postal-code').value = '';
  3644.       $('country').value = '';
  3645.       this.setMultiValueList_('phone-list', []);
  3646.       this.setMultiValueList_('email-list', []);
  3647.  
  3648.       this.countryChanged_();
  3649.     },
  3650.  
  3651.     /**
  3652.      * Loads the address data from |address|, sets the input fields based on
  3653.      * this data and stores the GUID of the address.
  3654.      * @private
  3655.      */
  3656.     loadAddress_: function(address) {
  3657.       this.setInputFields_(address);
  3658.       this.inputFieldChanged_();
  3659.       this.guid = address.guid;
  3660.     },
  3661.  
  3662.     /**
  3663.      * Sets the value of each input field according to |address|
  3664.      * @private
  3665.      */
  3666.     setInputFields_: function(address) {
  3667.       this.setMultiValueList_('full-name-list', address.fullName);
  3668.       $('company-name').value = address.companyName;
  3669.       $('addr-line-1').value = address.addrLine1;
  3670.       $('addr-line-2').value = address.addrLine2;
  3671.       $('city').value = address.city;
  3672.       $('state').value = address.state;
  3673.       $('postal-code').value = address.postalCode;
  3674.       $('country').value = address.country;
  3675.       this.setMultiValueList_('phone-list', address.phone);
  3676.       this.setMultiValueList_('email-list', address.email);
  3677.  
  3678.       this.countryChanged_();
  3679.     },
  3680.   };
  3681.  
  3682.   AutofillEditAddressOverlay.clearInputFields = function() {
  3683.     AutofillEditAddressOverlay.getInstance().clearInputFields_();
  3684.   };
  3685.  
  3686.   AutofillEditAddressOverlay.loadAddress = function(address) {
  3687.     AutofillEditAddressOverlay.getInstance().loadAddress_(address);
  3688.   };
  3689.  
  3690.   AutofillEditAddressOverlay.setTitle = function(title) {
  3691.     $('autofill-address-title').textContent = title;
  3692.   };
  3693.  
  3694.   AutofillEditAddressOverlay.setValidatedPhoneNumbers = function(numbers) {
  3695.     AutofillEditAddressOverlay.getInstance().setMultiValueList_('phone-list',
  3696.                                                                 numbers);
  3697.   };
  3698.  
  3699.   // Export
  3700.   return {
  3701.     AutofillEditAddressOverlay: AutofillEditAddressOverlay
  3702.   };
  3703. });
  3704.  
  3705. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3706. // Use of this source code is governed by a BSD-style license that can be
  3707. // found in the LICENSE file.
  3708.  
  3709. cr.define('options', function() {
  3710.   /** @const */ var OptionsPage = options.OptionsPage;
  3711.  
  3712.   // The GUID of the loaded credit card.
  3713.   var guid_;
  3714.  
  3715.   /**
  3716.    * AutofillEditCreditCardOverlay class
  3717.    * Encapsulated handling of the 'Add Page' overlay page.
  3718.    * @class
  3719.    */
  3720.   function AutofillEditCreditCardOverlay() {
  3721.     OptionsPage.call(this, 'autofillEditCreditCard',
  3722.                      loadTimeData.getString('autofillEditCreditCardTitle'),
  3723.                      'autofill-edit-credit-card-overlay');
  3724.   }
  3725.  
  3726.   cr.addSingletonGetter(AutofillEditCreditCardOverlay);
  3727.  
  3728.   AutofillEditCreditCardOverlay.prototype = {
  3729.     __proto__: OptionsPage.prototype,
  3730.  
  3731.     /**
  3732.      * Initializes the page.
  3733.      */
  3734.     initializePage: function() {
  3735.       OptionsPage.prototype.initializePage.call(this);
  3736.  
  3737.       var self = this;
  3738.       $('autofill-edit-credit-card-cancel-button').onclick = function(event) {
  3739.         self.dismissOverlay_();
  3740.       };
  3741.       $('autofill-edit-credit-card-apply-button').onclick = function(event) {
  3742.         self.saveCreditCard_();
  3743.         self.dismissOverlay_();
  3744.       };
  3745.  
  3746.       self.guid_ = '';
  3747.       self.hasEditedNumber_ = false;
  3748.       self.clearInputFields_();
  3749.       self.connectInputEvents_();
  3750.       self.setDefaultSelectOptions_();
  3751.     },
  3752.  
  3753.     /**
  3754.      * Clears any uncommitted input, and dismisses the overlay.
  3755.      * @private
  3756.      */
  3757.     dismissOverlay_: function() {
  3758.       this.clearInputFields_();
  3759.       this.guid_ = '';
  3760.       this.hasEditedNumber_ = false;
  3761.       OptionsPage.closeOverlay();
  3762.     },
  3763.  
  3764.     /**
  3765.      * Aggregates the values in the input fields into an array and sends the
  3766.      * array to the Autofill handler.
  3767.      * @private
  3768.      */
  3769.     saveCreditCard_: function() {
  3770.       var creditCard = new Array(5);
  3771.       creditCard[0] = this.guid_;
  3772.       creditCard[1] = $('name-on-card').value;
  3773.       creditCard[2] = $('credit-card-number').value;
  3774.       creditCard[3] = $('expiration-month').value;
  3775.       creditCard[4] = $('expiration-year').value;
  3776.       chrome.send('setCreditCard', creditCard);
  3777.     },
  3778.  
  3779.     /**
  3780.      * Connects each input field to the inputFieldChanged_() method that enables
  3781.      * or disables the 'Ok' button based on whether all the fields are empty or
  3782.      * not.
  3783.      * @private
  3784.      */
  3785.     connectInputEvents_: function() {
  3786.       var ccNumber = $('credit-card-number');
  3787.       $('name-on-card').oninput = ccNumber.oninput =
  3788.           $('expiration-month').onchange = $('expiration-year').onchange =
  3789.               this.inputFieldChanged_.bind(this);
  3790.     },
  3791.  
  3792.     /**
  3793.      * Checks the values of each of the input fields and disables the 'Ok'
  3794.      * button if all of the fields are empty.
  3795.      * @param {Event} opt_event Optional data for the 'input' event.
  3796.      * @private
  3797.      */
  3798.     inputFieldChanged_: function(opt_event) {
  3799.       var disabled = !$('name-on-card').value && !$('credit-card-number').value;
  3800.       $('autofill-edit-credit-card-apply-button').disabled = disabled;
  3801.     },
  3802.  
  3803.     /**
  3804.      * Sets the default values of the options in the 'Expiration date' select
  3805.      * controls.
  3806.      * @private
  3807.      */
  3808.     setDefaultSelectOptions_: function() {
  3809.       // Set the 'Expiration month' default options.
  3810.       var expirationMonth = $('expiration-month');
  3811.       expirationMonth.options.length = 0;
  3812.       for (var i = 1; i <= 12; ++i) {
  3813.         var text;
  3814.         if (i < 10)
  3815.           text = '0' + i;
  3816.         else
  3817.           text = i;
  3818.  
  3819.         var option = document.createElement('option');
  3820.         option.text = text;
  3821.         option.value = text;
  3822.         expirationMonth.add(option, null);
  3823.       }
  3824.  
  3825.       // Set the 'Expiration year' default options.
  3826.       var expirationYear = $('expiration-year');
  3827.       expirationYear.options.length = 0;
  3828.  
  3829.       var date = new Date();
  3830.       var year = parseInt(date.getFullYear());
  3831.       for (var i = 0; i < 10; ++i) {
  3832.         var text = year + i;
  3833.         var option = document.createElement('option');
  3834.         option.text = text;
  3835.         option.value = text;
  3836.         expirationYear.add(option, null);
  3837.       }
  3838.     },
  3839.  
  3840.     /**
  3841.      * Clears the value of each input field.
  3842.      * @private
  3843.      */
  3844.     clearInputFields_: function() {
  3845.       $('name-on-card').value = '';
  3846.       $('credit-card-number').value = '';
  3847.       $('expiration-month').selectedIndex = 0;
  3848.       $('expiration-year').selectedIndex = 0;
  3849.  
  3850.       // Reset the enabled status of the 'Ok' button.
  3851.       this.inputFieldChanged_();
  3852.     },
  3853.  
  3854.     /**
  3855.      * Sets the value of each input field according to |creditCard|
  3856.      * @private
  3857.      */
  3858.     setInputFields_: function(creditCard) {
  3859.       $('name-on-card').value = creditCard.nameOnCard;
  3860.       $('credit-card-number').value = creditCard.creditCardNumber;
  3861.  
  3862.       // The options for the year select control may be out-dated at this point,
  3863.       // e.g. the user opened the options page before midnight on New Year's Eve
  3864.       // and then loaded a credit card profile to edit in the new year, so
  3865.       // reload the select options just to be safe.
  3866.       this.setDefaultSelectOptions_();
  3867.  
  3868.       var idx = parseInt(creditCard.expirationMonth, 10);
  3869.       $('expiration-month').selectedIndex = idx - 1;
  3870.  
  3871.       expYear = creditCard.expirationYear;
  3872.       var date = new Date();
  3873.       var year = parseInt(date.getFullYear());
  3874.       for (var i = 0; i < 10; ++i) {
  3875.         var text = year + i;
  3876.         if (expYear == String(text))
  3877.           $('expiration-year').selectedIndex = i;
  3878.       }
  3879.     },
  3880.  
  3881.     /**
  3882.      * Loads the credit card data from |creditCard|, sets the input fields based
  3883.      * on this data and stores the GUID of the credit card.
  3884.      * @private
  3885.      */
  3886.     loadCreditCard_: function(creditCard) {
  3887.       this.setInputFields_(creditCard);
  3888.       this.inputFieldChanged_();
  3889.       this.guid_ = creditCard.guid;
  3890.     },
  3891.   };
  3892.  
  3893.   AutofillEditCreditCardOverlay.clearInputFields = function(title) {
  3894.     AutofillEditCreditCardOverlay.getInstance().clearInputFields_();
  3895.   };
  3896.  
  3897.   AutofillEditCreditCardOverlay.loadCreditCard = function(creditCard) {
  3898.     AutofillEditCreditCardOverlay.getInstance().loadCreditCard_(creditCard);
  3899.   };
  3900.  
  3901.   AutofillEditCreditCardOverlay.setTitle = function(title) {
  3902.     $('autofill-credit-card-title').textContent = title;
  3903.   };
  3904.  
  3905.   // Export
  3906.   return {
  3907.     AutofillEditCreditCardOverlay: AutofillEditCreditCardOverlay
  3908.   };
  3909. });
  3910.  
  3911. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  3912. // Use of this source code is governed by a BSD-style license that can be
  3913. // found in the LICENSE file.
  3914.  
  3915. cr.define('options.autofillOptions', function() {
  3916.   /** @const */ var DeletableItem = options.DeletableItem;
  3917.   /** @const */ var DeletableItemList = options.DeletableItemList;
  3918.   /** @const */ var InlineEditableItem = options.InlineEditableItem;
  3919.   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
  3920.  
  3921.   function AutofillEditProfileButton(guid, edit) {
  3922.     var editButtonEl = document.createElement('button');
  3923.     editButtonEl.className = 'list-inline-button custom-appearance';
  3924.     editButtonEl.textContent =
  3925.         loadTimeData.getString('autofillEditProfileButton');
  3926.     editButtonEl.onclick = function(e) { edit(guid); };
  3927.  
  3928.     // Don't select the row when clicking the button.
  3929.     editButtonEl.onmousedown = function(e) {
  3930.       e.stopPropagation();
  3931.     };
  3932.  
  3933.     return editButtonEl;
  3934.   }
  3935.  
  3936.   /**
  3937.    * Creates a new address list item.
  3938.    * @param {Array} entry An array of the form [guid, label].
  3939.    * @constructor
  3940.    * @extends {options.DeletableItem}
  3941.    */
  3942.   function AddressListItem(entry) {
  3943.     var el = cr.doc.createElement('div');
  3944.     el.guid = entry[0];
  3945.     el.label = entry[1];
  3946.     el.__proto__ = AddressListItem.prototype;
  3947.     el.decorate();
  3948.  
  3949.     return el;
  3950.   }
  3951.  
  3952.   AddressListItem.prototype = {
  3953.     __proto__: DeletableItem.prototype,
  3954.  
  3955.     /** @override */
  3956.     decorate: function() {
  3957.       DeletableItem.prototype.decorate.call(this);
  3958.  
  3959.       // The stored label.
  3960.       var label = this.ownerDocument.createElement('div');
  3961.       label.className = 'autofill-list-item';
  3962.       label.textContent = this.label;
  3963.       this.contentElement.appendChild(label);
  3964.  
  3965.       // The 'Edit' button.
  3966.       var editButtonEl = new AutofillEditProfileButton(
  3967.         this.guid,
  3968.         AutofillOptions.loadAddressEditor);
  3969.       this.contentElement.appendChild(editButtonEl);
  3970.     },
  3971.   };
  3972.  
  3973.   /**
  3974.    * Creates a new credit card list item.
  3975.    * @param {Array} entry An array of the form [guid, label, icon].
  3976.    * @constructor
  3977.    * @extends {options.DeletableItem}
  3978.    */
  3979.   function CreditCardListItem(entry) {
  3980.     var el = cr.doc.createElement('div');
  3981.     el.guid = entry[0];
  3982.     el.label = entry[1];
  3983.     el.icon = entry[2];
  3984.     el.description = entry[3];
  3985.     el.__proto__ = CreditCardListItem.prototype;
  3986.     el.decorate();
  3987.  
  3988.     return el;
  3989.   }
  3990.  
  3991.   CreditCardListItem.prototype = {
  3992.     __proto__: DeletableItem.prototype,
  3993.  
  3994.     /** @override */
  3995.     decorate: function() {
  3996.       DeletableItem.prototype.decorate.call(this);
  3997.  
  3998.       // The stored label.
  3999.       var label = this.ownerDocument.createElement('div');
  4000.       label.className = 'autofill-list-item';
  4001.       label.textContent = this.label;
  4002.       this.contentElement.appendChild(label);
  4003.  
  4004.       // The credit card icon.
  4005.       var icon = this.ownerDocument.createElement('image');
  4006.       icon.src = this.icon;
  4007.       icon.alt = this.description;
  4008.       this.contentElement.appendChild(icon);
  4009.  
  4010.       // The 'Edit' button.
  4011.       var editButtonEl = new AutofillEditProfileButton(
  4012.         this.guid,
  4013.         AutofillOptions.loadCreditCardEditor);
  4014.       this.contentElement.appendChild(editButtonEl);
  4015.     },
  4016.   };
  4017.  
  4018.   /**
  4019.    * Creates a new value list item.
  4020.    * @param {AutofillValuesList} list The parent list of this item.
  4021.    * @param {String} entry A string value.
  4022.    * @constructor
  4023.    * @extends {options.InlineEditableItem}
  4024.    */
  4025.   function ValuesListItem(list, entry) {
  4026.     var el = cr.doc.createElement('div');
  4027.     el.list = list;
  4028.     el.value = entry ? entry : '';
  4029.     el.__proto__ = ValuesListItem.prototype;
  4030.     el.decorate();
  4031.  
  4032.     return el;
  4033.   }
  4034.  
  4035.   ValuesListItem.prototype = {
  4036.     __proto__: InlineEditableItem.prototype,
  4037.  
  4038.     /** @override */
  4039.     decorate: function() {
  4040.       InlineEditableItem.prototype.decorate.call(this);
  4041.  
  4042.       // Note: This must be set prior to calling |createEditableTextCell|.
  4043.       this.isPlaceholder = !this.value;
  4044.  
  4045.       // The stored value.
  4046.       var cell = this.createEditableTextCell(this.value);
  4047.       this.contentElement.appendChild(cell);
  4048.       this.input = cell.querySelector('input');
  4049.  
  4050.       if (this.isPlaceholder) {
  4051.         this.input.placeholder = this.list.getAttribute('placeholder');
  4052.         this.deletable = false;
  4053.       }
  4054.  
  4055.       this.addEventListener('commitedit', this.onEditCommitted_);
  4056.     },
  4057.  
  4058.     /**
  4059.      * @return {string} This item's value.
  4060.      * @protected
  4061.      */
  4062.     value_: function() {
  4063.       return this.input.value;
  4064.     },
  4065.  
  4066.     /**
  4067.      * @param {Object} value The value to test.
  4068.      * @return {boolean} True if the given value is non-empty.
  4069.      * @protected
  4070.      */
  4071.     valueIsNonEmpty_: function(value) {
  4072.       return !!value;
  4073.     },
  4074.  
  4075.     /**
  4076.      * @return {boolean} True if value1 is logically equal to value2.
  4077.      */
  4078.     valuesAreEqual_: function(value1, value2) {
  4079.       return value1 === value2;
  4080.     },
  4081.  
  4082.     /**
  4083.      * Clears the item's value.
  4084.      * @protected
  4085.      */
  4086.     clearValue_: function() {
  4087.       this.input.value = '';
  4088.     },
  4089.  
  4090.     /**
  4091.      * Called when committing an edit.
  4092.      * If this is an "Add ..." item, committing a non-empty value adds that
  4093.      * value to the end of the values list, but also leaves this "Add ..." item
  4094.      * in place.
  4095.      * @param {Event} e The end event.
  4096.      * @private
  4097.      */
  4098.     onEditCommitted_: function(e) {
  4099.       var value = this.value_();
  4100.       var i = this.list.items.indexOf(this);
  4101.       if (i < this.list.dataModel.length &&
  4102.           this.valuesAreEqual_(value, this.list.dataModel.item(i))) {
  4103.         return;
  4104.       }
  4105.  
  4106.       var entries = this.list.dataModel.slice();
  4107.       if (this.valueIsNonEmpty_(value) &&
  4108.           !entries.some(this.valuesAreEqual_.bind(this, value))) {
  4109.         // Update with new value.
  4110.         if (this.isPlaceholder) {
  4111.           // It is important that updateIndex is done before validateAndSave.
  4112.           // Otherwise we can not be sure about AddRow index.
  4113.           this.list.dataModel.updateIndex(i);
  4114.           this.list.validateAndSave(i, 0, value);
  4115.         } else {
  4116.           this.list.validateAndSave(i, 1, value);
  4117.         }
  4118.       } else {
  4119.         // Reject empty values and duplicates.
  4120.         if (!this.isPlaceholder)
  4121.           this.list.dataModel.splice(i, 1);
  4122.         else
  4123.           this.clearValue_();
  4124.       }
  4125.     },
  4126.   };
  4127.  
  4128.   /**
  4129.    * Creates a new name value list item.
  4130.    * @param {AutofillNameValuesList} list The parent list of this item.
  4131.    * @param {array} entry An array of [first, middle, last] names.
  4132.    * @constructor
  4133.    * @extends {options.ValuesListItem}
  4134.    */
  4135.   function NameListItem(list, entry) {
  4136.     var el = cr.doc.createElement('div');
  4137.     el.list = list;
  4138.     el.first = entry ? entry[0] : '';
  4139.     el.middle = entry ? entry[1] : '';
  4140.     el.last = entry ? entry[2] : '';
  4141.     el.__proto__ = NameListItem.prototype;
  4142.     el.decorate();
  4143.  
  4144.     return el;
  4145.   }
  4146.  
  4147.   NameListItem.prototype = {
  4148.     __proto__: ValuesListItem.prototype,
  4149.  
  4150.     /** @override */
  4151.     decorate: function() {
  4152.       InlineEditableItem.prototype.decorate.call(this);
  4153.  
  4154.       // Note: This must be set prior to calling |createEditableTextCell|.
  4155.       this.isPlaceholder = !this.first && !this.middle && !this.last;
  4156.  
  4157.       // The stored value.
  4158.       // For the simulated static "input element" to display correctly, the
  4159.       // value must not be empty.  We use a space to force the UI to render
  4160.       // correctly when the value is logically empty.
  4161.       var cell = this.createEditableTextCell(this.first);
  4162.       this.contentElement.appendChild(cell);
  4163.       this.firstNameInput = cell.querySelector('input');
  4164.  
  4165.       cell = this.createEditableTextCell(this.middle);
  4166.       this.contentElement.appendChild(cell);
  4167.       this.middleNameInput = cell.querySelector('input');
  4168.  
  4169.       cell = this.createEditableTextCell(this.last);
  4170.       this.contentElement.appendChild(cell);
  4171.       this.lastNameInput = cell.querySelector('input');
  4172.  
  4173.       if (this.isPlaceholder) {
  4174.         this.firstNameInput.placeholder =
  4175.             loadTimeData.getString('autofillAddFirstNamePlaceholder');
  4176.         this.middleNameInput.placeholder =
  4177.             loadTimeData.getString('autofillAddMiddleNamePlaceholder');
  4178.         this.lastNameInput.placeholder =
  4179.             loadTimeData.getString('autofillAddLastNamePlaceholder');
  4180.         this.deletable = false;
  4181.       }
  4182.  
  4183.       this.addEventListener('commitedit', this.onEditCommitted_);
  4184.     },
  4185.  
  4186.     /** @override */
  4187.     value_: function() {
  4188.       return [this.firstNameInput.value,
  4189.               this.middleNameInput.value,
  4190.               this.lastNameInput.value];
  4191.     },
  4192.  
  4193.     /** @override */
  4194.     valueIsNonEmpty_: function(value) {
  4195.       return value[0] || value[1] || value[2];
  4196.     },
  4197.  
  4198.     /** @override */
  4199.     valuesAreEqual_: function(value1, value2) {
  4200.       // First, check for null values.
  4201.       if (!value1 || !value2)
  4202.         return value1 == value2;
  4203.  
  4204.       return value1[0] === value2[0] &&
  4205.              value1[1] === value2[1] &&
  4206.              value1[2] === value2[2];
  4207.     },
  4208.  
  4209.     /** @override */
  4210.     clearValue_: function() {
  4211.       this.firstNameInput.value = '';
  4212.       this.middleNameInput.value = '';
  4213.       this.lastNameInput.value = '';
  4214.     },
  4215.   };
  4216.  
  4217.   /**
  4218.    * Base class for shared implementation between address and credit card lists.
  4219.    * @constructor
  4220.    * @extends {options.DeletableItemList}
  4221.    */
  4222.   var AutofillProfileList = cr.ui.define('list');
  4223.  
  4224.   AutofillProfileList.prototype = {
  4225.     __proto__: DeletableItemList.prototype,
  4226.  
  4227.     decorate: function() {
  4228.       DeletableItemList.prototype.decorate.call(this);
  4229.  
  4230.       this.addEventListener('blur', this.onBlur_);
  4231.     },
  4232.  
  4233.     /**
  4234.      * When the list loses focus, unselect all items in the list.
  4235.      * @private
  4236.      */
  4237.     onBlur_: function() {
  4238.       this.selectionModel.unselectAll();
  4239.     },
  4240.   };
  4241.  
  4242.   /**
  4243.    * Create a new address list.
  4244.    * @constructor
  4245.    * @extends {options.AutofillProfileList}
  4246.    */
  4247.   var AutofillAddressList = cr.ui.define('list');
  4248.  
  4249.   AutofillAddressList.prototype = {
  4250.     __proto__: AutofillProfileList.prototype,
  4251.  
  4252.     decorate: function() {
  4253.       AutofillProfileList.prototype.decorate.call(this);
  4254.     },
  4255.  
  4256.     /** @override */
  4257.     activateItemAtIndex: function(index) {
  4258.       AutofillOptions.loadAddressEditor(this.dataModel.item(index)[0]);
  4259.     },
  4260.  
  4261.     /** @override */
  4262.     createItem: function(entry) {
  4263.       return new AddressListItem(entry);
  4264.     },
  4265.  
  4266.     /** @override */
  4267.     deleteItemAtIndex: function(index) {
  4268.       AutofillOptions.removeData(this.dataModel.item(index)[0]);
  4269.     },
  4270.   };
  4271.  
  4272.   /**
  4273.    * Create a new credit card list.
  4274.    * @constructor
  4275.    * @extends {options.DeletableItemList}
  4276.    */
  4277.   var AutofillCreditCardList = cr.ui.define('list');
  4278.  
  4279.   AutofillCreditCardList.prototype = {
  4280.     __proto__: AutofillProfileList.prototype,
  4281.  
  4282.     decorate: function() {
  4283.       AutofillProfileList.prototype.decorate.call(this);
  4284.     },
  4285.  
  4286.     /** @override */
  4287.     activateItemAtIndex: function(index) {
  4288.       AutofillOptions.loadCreditCardEditor(this.dataModel.item(index)[0]);
  4289.     },
  4290.  
  4291.     /** @override */
  4292.     createItem: function(entry) {
  4293.       return new CreditCardListItem(entry);
  4294.     },
  4295.  
  4296.     /** @override */
  4297.     deleteItemAtIndex: function(index) {
  4298.       AutofillOptions.removeData(this.dataModel.item(index)[0]);
  4299.     },
  4300.   };
  4301.  
  4302.   /**
  4303.    * Create a new value list.
  4304.    * @constructor
  4305.    * @extends {options.InlineEditableItemList}
  4306.    */
  4307.   var AutofillValuesList = cr.ui.define('list');
  4308.  
  4309.   AutofillValuesList.prototype = {
  4310.     __proto__: InlineEditableItemList.prototype,
  4311.  
  4312.     /** @override */
  4313.     createItem: function(entry) {
  4314.       return new ValuesListItem(this, entry);
  4315.     },
  4316.  
  4317.     /** @override */
  4318.     deleteItemAtIndex: function(index) {
  4319.       this.dataModel.splice(index, 1);
  4320.     },
  4321.  
  4322.     /** @override */
  4323.     shouldFocusPlaceholder: function() {
  4324.       return false;
  4325.     },
  4326.  
  4327.     /**
  4328.      * Called when the list hierarchy as a whole loses or gains focus.
  4329.      * If the list was focused in response to a mouse click, call into the
  4330.      * superclass's implementation.  If the list was focused in response to a
  4331.      * keyboard navigation, focus the first item.
  4332.      * If the list loses focus, unselect all the elements.
  4333.      * @param {Event} e The change event.
  4334.      * @private
  4335.      */
  4336.     handleListFocusChange_: function(e) {
  4337.       // We check to see whether there is a selected item as a proxy for
  4338.       // distinguishing between mouse- and keyboard-originated focus events.
  4339.       var selectedItem = this.selectedItem;
  4340.       if (selectedItem)
  4341.         InlineEditableItemList.prototype.handleListFocusChange_.call(this, e);
  4342.  
  4343.       if (!e.newValue) {
  4344.         // When the list loses focus, unselect all the elements.
  4345.         this.selectionModel.unselectAll();
  4346.       } else {
  4347.         // When the list gains focus, select the first item if nothing else is
  4348.         // selected.
  4349.         var firstItem = this.getListItemByIndex(0);
  4350.         if (!selectedItem && firstItem && e.newValue)
  4351.           firstItem.handleFocus_();
  4352.       }
  4353.     },
  4354.  
  4355.     /**
  4356.      * Called when a new list item should be validated; subclasses are
  4357.      * responsible for implementing if validation is required.
  4358.      * @param {number} index The index of the item that was inserted or changed.
  4359.      * @param {number} remove The number items to remove.
  4360.      * @param {string} value The value of the item to insert.
  4361.      */
  4362.     validateAndSave: function(index, remove, value) {
  4363.       this.dataModel.splice(index, remove, value);
  4364.     },
  4365.   };
  4366.  
  4367.   /**
  4368.    * Create a new value list for phone number validation.
  4369.    * @constructor
  4370.    * @extends {options.AutofillValuesList}
  4371.    */
  4372.   var AutofillNameValuesList = cr.ui.define('list');
  4373.  
  4374.   AutofillNameValuesList.prototype = {
  4375.     __proto__: AutofillValuesList.prototype,
  4376.  
  4377.     /** @override */
  4378.     createItem: function(entry) {
  4379.       return new NameListItem(this, entry);
  4380.     },
  4381.   };
  4382.  
  4383.   /**
  4384.    * Create a new value list for phone number validation.
  4385.    * @constructor
  4386.    * @extends {options.AutofillValuesList}
  4387.    */
  4388.   var AutofillPhoneValuesList = cr.ui.define('list');
  4389.  
  4390.   AutofillPhoneValuesList.prototype = {
  4391.     __proto__: AutofillValuesList.prototype,
  4392.  
  4393.     /** @override */
  4394.     validateAndSave: function(index, remove, value) {
  4395.       var numbers = this.dataModel.slice(0, this.dataModel.length - 1);
  4396.       numbers.splice(index, remove, value);
  4397.       var info = new Array();
  4398.       info[0] = index;
  4399.       info[1] = numbers;
  4400.       info[2] = $('country').value;
  4401.       chrome.send('validatePhoneNumbers', info);
  4402.     },
  4403.   };
  4404.  
  4405.   return {
  4406.     AddressListItem: AddressListItem,
  4407.     CreditCardListItem: CreditCardListItem,
  4408.     ValuesListItem: ValuesListItem,
  4409.     NameListItem: NameListItem,
  4410.     AutofillAddressList: AutofillAddressList,
  4411.     AutofillCreditCardList: AutofillCreditCardList,
  4412.     AutofillValuesList: AutofillValuesList,
  4413.     AutofillNameValuesList: AutofillNameValuesList,
  4414.     AutofillPhoneValuesList: AutofillPhoneValuesList,
  4415.   };
  4416. });
  4417.  
  4418. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4419. // Use of this source code is governed by a BSD-style license that can be
  4420. // found in the LICENSE file.
  4421.  
  4422. cr.define('options', function() {
  4423.   var OptionsPage = options.OptionsPage;
  4424.   var ArrayDataModel = cr.ui.ArrayDataModel;
  4425.  
  4426.   /////////////////////////////////////////////////////////////////////////////
  4427.   // AutofillOptions class:
  4428.  
  4429.   /**
  4430.    * Encapsulated handling of Autofill options page.
  4431.    * @constructor
  4432.    */
  4433.   function AutofillOptions() {
  4434.     OptionsPage.call(this,
  4435.                      'autofill',
  4436.                      loadTimeData.getString('autofillOptionsPageTabTitle'),
  4437.                      'autofill-options');
  4438.   }
  4439.  
  4440.   cr.addSingletonGetter(AutofillOptions);
  4441.  
  4442.   AutofillOptions.prototype = {
  4443.     __proto__: OptionsPage.prototype,
  4444.  
  4445.     /**
  4446.      * The address list.
  4447.      * @type {DeletableItemList}
  4448.      * @private
  4449.      */
  4450.     addressList_: null,
  4451.  
  4452.     /**
  4453.      * The credit card list.
  4454.      * @type {DeletableItemList}
  4455.      * @private
  4456.      */
  4457.     creditCardList_: null,
  4458.  
  4459.     initializePage: function() {
  4460.       OptionsPage.prototype.initializePage.call(this);
  4461.  
  4462.       this.createAddressList_();
  4463.       this.createCreditCardList_();
  4464.  
  4465.       var self = this;
  4466.       $('autofill-add-address').onclick = function(event) {
  4467.         self.showAddAddressOverlay_();
  4468.       };
  4469.       $('autofill-add-creditcard').onclick = function(event) {
  4470.         self.showAddCreditCardOverlay_();
  4471.       };
  4472.       $('autofill-options-confirm').onclick = function(event) {
  4473.         OptionsPage.closeOverlay();
  4474.       };
  4475.  
  4476.       // TODO(jhawkins): What happens when Autofill is disabled whilst on the
  4477.       // Autofill options page?
  4478.     },
  4479.  
  4480.     /**
  4481.      * Creates, decorates and initializes the address list.
  4482.      * @private
  4483.      */
  4484.     createAddressList_: function() {
  4485.       this.addressList_ = $('address-list');
  4486.       options.autofillOptions.AutofillAddressList.decorate(this.addressList_);
  4487.       this.addressList_.autoExpands = true;
  4488.     },
  4489.  
  4490.     /**
  4491.      * Creates, decorates and initializes the credit card list.
  4492.      * @private
  4493.      */
  4494.     createCreditCardList_: function() {
  4495.       this.creditCardList_ = $('creditcard-list');
  4496.       options.autofillOptions.AutofillCreditCardList.decorate(
  4497.           this.creditCardList_);
  4498.       this.creditCardList_.autoExpands = true;
  4499.     },
  4500.  
  4501.     /**
  4502.      * Shows the 'Add address' overlay, specifically by loading the
  4503.      * 'Edit address' overlay, emptying the input fields and modifying the
  4504.      * overlay title.
  4505.      * @private
  4506.      */
  4507.     showAddAddressOverlay_: function() {
  4508.       var title = loadTimeData.getString('addAddressTitle');
  4509.       AutofillEditAddressOverlay.setTitle(title);
  4510.       AutofillEditAddressOverlay.clearInputFields();
  4511.       OptionsPage.navigateToPage('autofillEditAddress');
  4512.     },
  4513.  
  4514.     /**
  4515.      * Shows the 'Add credit card' overlay, specifically by loading the
  4516.      * 'Edit credit card' overlay, emptying the input fields and modifying the
  4517.      * overlay title.
  4518.      * @private
  4519.      */
  4520.     showAddCreditCardOverlay_: function() {
  4521.       var title = loadTimeData.getString('addCreditCardTitle');
  4522.       AutofillEditCreditCardOverlay.setTitle(title);
  4523.       AutofillEditCreditCardOverlay.clearInputFields();
  4524.       OptionsPage.navigateToPage('autofillEditCreditCard');
  4525.     },
  4526.  
  4527.     /**
  4528.      * Updates the data model for the address list with the values from
  4529.      * |entries|.
  4530.      * @param {Array} entries The list of addresses.
  4531.      */
  4532.     setAddressList_: function(entries) {
  4533.       this.addressList_.dataModel = new ArrayDataModel(entries);
  4534.     },
  4535.  
  4536.     /**
  4537.      * Updates the data model for the credit card list with the values from
  4538.      * |entries|.
  4539.      * @param {Array} entries The list of credit cards.
  4540.      */
  4541.     setCreditCardList_: function(entries) {
  4542.       this.creditCardList_.dataModel = new ArrayDataModel(entries);
  4543.     },
  4544.  
  4545.     /**
  4546.      * Removes the Autofill address or credit card represented by |guid|.
  4547.      * @param {String} guid The GUID of the address to remove.
  4548.      * @private
  4549.      */
  4550.     removeData_: function(guid) {
  4551.       chrome.send('removeData', [guid]);
  4552.     },
  4553.  
  4554.     /**
  4555.      * Requests profile data for the address represented by |guid| from the
  4556.      * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
  4557.      * calls showEditAddressOverlay().
  4558.      * @param {String} guid The GUID of the address to edit.
  4559.      * @private
  4560.      */
  4561.     loadAddressEditor_: function(guid) {
  4562.       chrome.send('loadAddressEditor', [guid]);
  4563.     },
  4564.  
  4565.     /**
  4566.      * Requests profile data for the credit card represented by |guid| from the
  4567.      * PersonalDataManager. Once the data is loaded, the AutofillOptionsHandler
  4568.      * calls showEditCreditCardOverlay().
  4569.      * @param {String} guid The GUID of the credit card to edit.
  4570.      * @private
  4571.      */
  4572.     loadCreditCardEditor_: function(guid) {
  4573.       chrome.send('loadCreditCardEditor', [guid]);
  4574.     },
  4575.  
  4576.     /**
  4577.      * Shows the 'Edit address' overlay, using the data in |address| to fill the
  4578.      * input fields. |address| is a list with one item, an associative array
  4579.      * that contains the address data.
  4580.      * @private
  4581.      */
  4582.     showEditAddressOverlay_: function(address) {
  4583.       var title = loadTimeData.getString('editAddressTitle');
  4584.       AutofillEditAddressOverlay.setTitle(title);
  4585.       AutofillEditAddressOverlay.loadAddress(address);
  4586.       OptionsPage.navigateToPage('autofillEditAddress');
  4587.     },
  4588.  
  4589.     /**
  4590.      * Shows the 'Edit credit card' overlay, using the data in |credit_card| to
  4591.      * fill the input fields. |address| is a list with one item, an associative
  4592.      * array that contains the credit card data.
  4593.      * @private
  4594.      */
  4595.     showEditCreditCardOverlay_: function(creditCard) {
  4596.       var title = loadTimeData.getString('editCreditCardTitle');
  4597.       AutofillEditCreditCardOverlay.setTitle(title);
  4598.       AutofillEditCreditCardOverlay.loadCreditCard(creditCard);
  4599.       OptionsPage.navigateToPage('autofillEditCreditCard');
  4600.     },
  4601.   };
  4602.  
  4603.   AutofillOptions.setAddressList = function(entries) {
  4604.     AutofillOptions.getInstance().setAddressList_(entries);
  4605.   };
  4606.  
  4607.   AutofillOptions.setCreditCardList = function(entries) {
  4608.     AutofillOptions.getInstance().setCreditCardList_(entries);
  4609.   };
  4610.  
  4611.   AutofillOptions.removeData = function(guid) {
  4612.     AutofillOptions.getInstance().removeData_(guid);
  4613.   };
  4614.  
  4615.   AutofillOptions.loadAddressEditor = function(guid) {
  4616.     AutofillOptions.getInstance().loadAddressEditor_(guid);
  4617.   };
  4618.  
  4619.   AutofillOptions.loadCreditCardEditor = function(guid) {
  4620.     AutofillOptions.getInstance().loadCreditCardEditor_(guid);
  4621.   };
  4622.  
  4623.   AutofillOptions.editAddress = function(address) {
  4624.     AutofillOptions.getInstance().showEditAddressOverlay_(address);
  4625.   };
  4626.  
  4627.   AutofillOptions.editCreditCard = function(creditCard) {
  4628.     AutofillOptions.getInstance().showEditCreditCardOverlay_(creditCard);
  4629.   };
  4630.  
  4631.   // Export
  4632.   return {
  4633.     AutofillOptions: AutofillOptions
  4634.   };
  4635.  
  4636. });
  4637.  
  4638.  
  4639. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4640. // Use of this source code is governed by a BSD-style license that can be
  4641. // found in the LICENSE file.
  4642.  
  4643. cr.define('options', function() {
  4644.   var OptionsPage = options.OptionsPage;
  4645.   var ArrayDataModel = cr.ui.ArrayDataModel;
  4646.   var RepeatingButton = cr.ui.RepeatingButton;
  4647.  
  4648.   //
  4649.   // BrowserOptions class
  4650.   // Encapsulated handling of browser options page.
  4651.   //
  4652.   function BrowserOptions() {
  4653.     OptionsPage.call(this, 'settings', loadTimeData.getString('settingsTitle'),
  4654.                      'settings');
  4655.   }
  4656.  
  4657.   cr.addSingletonGetter(BrowserOptions);
  4658.  
  4659.   BrowserOptions.prototype = {
  4660.     __proto__: options.OptionsPage.prototype,
  4661.  
  4662.     // State variables.
  4663.     syncSetupCompleted: false,
  4664.  
  4665.     /**
  4666.      * Keeps track of whether |onShowHomeButtonChanged_| has been called. See
  4667.      * |onShowHomeButtonChanged_|.
  4668.      * @type {bool}
  4669.      * @private
  4670.      */
  4671.     onShowHomeButtonChangedCalled_: false,
  4672.  
  4673.     /**
  4674.      * Track if page initialization is complete.  All C++ UI handlers have the
  4675.      * chance to manipulate page content within their InitializePage mathods.
  4676.      * This flag is set to true after all initializers have been called.
  4677.      * @type (boolean}
  4678.      * @private
  4679.      */
  4680.     initializationComplete_: false,
  4681.  
  4682.     /** @override */
  4683.     initializePage: function() {
  4684.       OptionsPage.prototype.initializePage.call(this);
  4685.       var self = this;
  4686.  
  4687.       // Ensure that navigation events are unblocked on uber page. A reload of
  4688.       // the settings page while an overlay is open would otherwise leave uber
  4689.       // page in a blocked state, where tab switching is not possible.
  4690.       uber.invokeMethodOnParent('stopInterceptingEvents');
  4691.  
  4692.       window.addEventListener('message', this.handleWindowMessage_.bind(this));
  4693.  
  4694.       $('advanced-settings-expander').onclick = function() {
  4695.         self.toggleSectionWithAnimation_(
  4696.             $('advanced-settings'),
  4697.             $('advanced-settings-container'));
  4698.  
  4699.         // If the link was focused (i.e., it was activated using the keyboard)
  4700.         // and it was used to show the section (rather than hiding it), focus
  4701.         // the first element in the container.
  4702.         if (document.activeElement === $('advanced-settings-expander') &&
  4703.                 $('advanced-settings').style.height === '') {
  4704.           var focusElement = $('advanced-settings-container').querySelector(
  4705.               'button, input, list, select, a[href]');
  4706.           if (focusElement)
  4707.             focusElement.focus();
  4708.         }
  4709.       }
  4710.  
  4711.       $('advanced-settings').addEventListener('webkitTransitionEnd',
  4712.           this.updateAdvancedSettingsExpander_.bind(this));
  4713.  
  4714.       if (cr.isChromeOS)
  4715.         UIAccountTweaks.applyGuestModeVisibility(document);
  4716.  
  4717.       // Sync (Sign in) section.
  4718.       this.updateSyncState_(loadTimeData.getValue('syncData'));
  4719.  
  4720.       $('start-stop-sync').onclick = function(event) {
  4721.         if (self.syncSetupCompleted)
  4722.           SyncSetupOverlay.showStopSyncingUI();
  4723.         else if (cr.isChromeOS)
  4724.           SyncSetupOverlay.showSetupUIWithoutLogin();
  4725.         else
  4726.           SyncSetupOverlay.showSetupUI();
  4727.       };
  4728.       $('customize-sync').onclick = function(event) {
  4729.         if (cr.isChromeOS)
  4730.           SyncSetupOverlay.showSetupUIWithoutLogin();
  4731.         else
  4732.           SyncSetupOverlay.showSetupUI();
  4733.       };
  4734.  
  4735.       // Internet connection section (ChromeOS only).
  4736.       if (cr.isChromeOS) {
  4737.         options.network.NetworkList.decorate($('network-list'));
  4738.         options.network.NetworkList.refreshNetworkData(
  4739.             loadTimeData.getValue('networkData'));
  4740.       }
  4741.  
  4742.       // On Startup section.
  4743.       Preferences.getInstance().addEventListener('session.restore_on_startup',
  4744.           this.onRestoreOnStartupChanged_.bind(this));
  4745.       Preferences.getInstance().addEventListener(
  4746.           'session.urls_to_restore_on_startup',
  4747.           function(event) {
  4748.             $('startup-set-pages').disabled = event.value.disabled;
  4749.           });
  4750.  
  4751.       $('startup-set-pages').onclick = function() {
  4752.         OptionsPage.navigateToPage('startup');
  4753.       };
  4754.  
  4755.       // Appearance section.
  4756.       Preferences.getInstance().addEventListener('browser.show_home_button',
  4757.           this.onShowHomeButtonChanged_.bind(this));
  4758.  
  4759.       Preferences.getInstance().addEventListener('homepage',
  4760.           this.onHomePageChanged_.bind(this));
  4761.       Preferences.getInstance().addEventListener('homepage_is_newtabpage',
  4762.           this.onHomePageIsNtpChanged_.bind(this));
  4763.  
  4764.       $('change-home-page').onclick = function(event) {
  4765.         OptionsPage.navigateToPage('homePageOverlay');
  4766.       };
  4767.  
  4768.       if ($('set-wallpaper')) {
  4769.         $('set-wallpaper').onclick = function(event) {
  4770.           chrome.send('openWallpaperManager');
  4771.         };
  4772.       }
  4773.  
  4774.       $('themes-gallery').onclick = function(event) {
  4775.         window.open(loadTimeData.getString('themesGalleryURL'));
  4776.       };
  4777.       $('themes-reset').onclick = function(event) {
  4778.         chrome.send('themesReset');
  4779.       };
  4780.  
  4781.       // Device section (ChromeOS only).
  4782.       if (cr.isChromeOS) {
  4783.         $('keyboard-settings-button').onclick = function(evt) {
  4784.           OptionsPage.navigateToPage('keyboard-overlay');
  4785.         };
  4786.         $('pointer-settings-button').onclick = function(evt) {
  4787.           OptionsPage.navigateToPage('pointer-overlay');
  4788.         };
  4789.       }
  4790.  
  4791.       // Search section.
  4792.       $('manage-default-search-engines').onclick = function(event) {
  4793.         OptionsPage.navigateToPage('searchEngines');
  4794.         chrome.send('coreOptionsUserMetricsAction',
  4795.                     ['Options_ManageSearchEngines']);
  4796.       };
  4797.       $('default-search-engine').addEventListener('change',
  4798.           this.setDefaultSearchEngine_);
  4799.       if (loadTimeData.getValue('instant_enabled') ==
  4800.           'instant_extended.enabled') {
  4801.         // We don't want to see the confirm dialog for instant extended.
  4802.         $('instant-enabled-control').removeAttribute('dialog-pref');
  4803.         $('instant-enabled-indicator').removeAttribute('dialog-pref');
  4804.       }
  4805.  
  4806.       // Users section.
  4807.       if (loadTimeData.valueExists('profilesInfo')) {
  4808.         $('profiles-section').hidden = false;
  4809.  
  4810.         var profilesList = $('profiles-list');
  4811.         options.browser_options.ProfileList.decorate(profilesList);
  4812.         profilesList.autoExpands = true;
  4813.  
  4814.         this.setProfilesInfo_(loadTimeData.getValue('profilesInfo'));
  4815.  
  4816.         profilesList.addEventListener('change',
  4817.             this.setProfileViewButtonsStatus_);
  4818.         $('profiles-create').onclick = function(event) {
  4819.           chrome.send('createProfileInfo');
  4820.         };
  4821.         $('profiles-manage').onclick = function(event) {
  4822.           ManageProfileOverlay.showManageDialog();
  4823.         };
  4824.         $('profiles-delete').onclick = function(event) {
  4825.           var selectedProfile = self.getSelectedProfileItem_();
  4826.           if (selectedProfile)
  4827.             ManageProfileOverlay.showDeleteDialog(selectedProfile);
  4828.         };
  4829.       }
  4830.  
  4831.       if (cr.isChromeOS) {
  4832.         if (!UIAccountTweaks.loggedInAsGuest()) {
  4833.           $('account-picture-wrapper').onclick = function(event) {
  4834.             OptionsPage.navigateToPage('changePicture');
  4835.           };
  4836.         }
  4837.  
  4838.         // Username (canonical email) of the currently logged in user or
  4839.         // |kGuestUser| if a guest session is active.
  4840.         this.username_ = loadTimeData.getString('username');
  4841.  
  4842.         this.updateAccountPicture_();
  4843.  
  4844.         $('manage-accounts-button').onclick = function(event) {
  4845.           OptionsPage.navigateToPage('accounts');
  4846.           chrome.send('coreOptionsUserMetricsAction',
  4847.               ['Options_ManageAccounts']);
  4848.         };
  4849.       } else {
  4850.         $('import-data').onclick = function(event) {
  4851.           ImportDataOverlay.show();
  4852.           chrome.send('coreOptionsUserMetricsAction', ['Import_ShowDlg']);
  4853.         };
  4854.  
  4855.         if ($('themes-GTK-button')) {
  4856.           $('themes-GTK-button').onclick = function(event) {
  4857.             chrome.send('themesSetGTK');
  4858.           };
  4859.         }
  4860.       }
  4861.  
  4862.       // Default browser section.
  4863.       if (!cr.isChromeOS) {
  4864.         $('set-as-default-browser').onclick = function(event) {
  4865.           chrome.send('becomeDefaultBrowser');
  4866.         };
  4867.  
  4868.         $('auto-launch').onclick = this.handleAutoLaunchChanged_;
  4869.       }
  4870.  
  4871.       // Privacy section.
  4872.       $('privacyContentSettingsButton').onclick = function(event) {
  4873.         OptionsPage.navigateToPage('content');
  4874.         OptionsPage.showTab($('cookies-nav-tab'));
  4875.         chrome.send('coreOptionsUserMetricsAction',
  4876.             ['Options_ContentSettings']);
  4877.       };
  4878.       $('privacyClearDataButton').onclick = function(event) {
  4879.         OptionsPage.navigateToPage('clearBrowserData');
  4880.         chrome.send('coreOptionsUserMetricsAction', ['Options_ClearData']);
  4881.       };
  4882.       // 'metricsReportingEnabled' element is only present on Chrome branded
  4883.       // builds.
  4884.       if ($('metricsReportingEnabled')) {
  4885.         $('metricsReportingEnabled').onclick = function(event) {
  4886.           chrome.send('metricsReportingCheckboxAction',
  4887.               [String(event.currentTarget.checked)]);
  4888.         };
  4889.       }
  4890.  
  4891.       // Bluetooth (CrOS only).
  4892.       if (cr.isChromeOS) {
  4893.         options.system.bluetooth.BluetoothDeviceList.decorate(
  4894.             $('bluetooth-paired-devices-list'));
  4895.  
  4896.         $('bluetooth-add-device').onclick =
  4897.             this.handleAddBluetoothDevice_.bind(this);
  4898.  
  4899.         $('enable-bluetooth').onchange = function(event) {
  4900.           var state = $('enable-bluetooth').checked;
  4901.           chrome.send('bluetoothEnableChange', [Boolean(state)]);
  4902.         };
  4903.  
  4904.         $('bluetooth-reconnect-device').onclick = function(event) {
  4905.           var device = $('bluetooth-paired-devices-list').selectedItem;
  4906.           var address = device.address;
  4907.           chrome.send('updateBluetoothDevice', [address, 'connect']);
  4908.           OptionsPage.closeOverlay();
  4909.         };
  4910.  
  4911.         $('bluetooth-reconnect-device').onmousedown = function(event) {
  4912.           // Prevent 'blur' event, which would reset the list selection,
  4913.           // thereby disabling the apply button.
  4914.           event.preventDefault();
  4915.         };
  4916.  
  4917.         $('bluetooth-paired-devices-list').addEventListener('change',
  4918.             function() {
  4919.           var item = $('bluetooth-paired-devices-list').selectedItem;
  4920.           var disabled = !item || item.connected;
  4921.           $('bluetooth-reconnect-device').disabled = disabled;
  4922.         });
  4923.       }
  4924.  
  4925.       // Passwords and Forms section.
  4926.       $('autofill-settings').onclick = function(event) {
  4927.         OptionsPage.navigateToPage('autofill');
  4928.         chrome.send('coreOptionsUserMetricsAction',
  4929.             ['Options_ShowAutofillSettings']);
  4930.       };
  4931.       $('manage-passwords').onclick = function(event) {
  4932.         OptionsPage.navigateToPage('passwords');
  4933.         OptionsPage.showTab($('passwords-nav-tab'));
  4934.         chrome.send('coreOptionsUserMetricsAction',
  4935.             ['Options_ShowPasswordManager']);
  4936.       };
  4937.       if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
  4938.         // Disable and turn off Autofill in guest mode.
  4939.         var autofillEnabled = $('autofill-enabled');
  4940.         autofillEnabled.disabled = true;
  4941.         autofillEnabled.checked = false;
  4942.         cr.dispatchSimpleEvent(autofillEnabled, 'change');
  4943.         $('autofill-settings').disabled = true;
  4944.  
  4945.         // Disable and turn off Password Manager in guest mode.
  4946.         var passwordManagerEnabled = $('password-manager-enabled');
  4947.         passwordManagerEnabled.disabled = true;
  4948.         passwordManagerEnabled.checked = false;
  4949.         cr.dispatchSimpleEvent(passwordManagerEnabled, 'change');
  4950.         $('manage-passwords').disabled = true;
  4951.       }
  4952.  
  4953.       if (cr.isMac) {
  4954.         $('mac-passwords-warning').hidden =
  4955.             !loadTimeData.getBoolean('multiple_profiles');
  4956.       }
  4957.  
  4958.       // Network section.
  4959.       if (!cr.isChromeOS) {
  4960.         $('proxiesConfigureButton').onclick = function(event) {
  4961.           chrome.send('showNetworkProxySettings');
  4962.         };
  4963.       }
  4964.  
  4965.       // Web Content section.
  4966.       $('fontSettingsCustomizeFontsButton').onclick = function(event) {
  4967.         OptionsPage.navigateToPage('fonts');
  4968.         chrome.send('coreOptionsUserMetricsAction', ['Options_FontSettings']);
  4969.       };
  4970.       $('defaultFontSize').onchange = function(event) {
  4971.         var value = event.target.options[event.target.selectedIndex].value;
  4972.         Preferences.setIntegerPref(
  4973.              'webkit.webprefs.default_fixed_font_size',
  4974.              value - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
  4975.         chrome.send('defaultFontSizeAction', [String(value)]);
  4976.       };
  4977.       $('defaultZoomFactor').onchange = function(event) {
  4978.         chrome.send('defaultZoomFactorAction',
  4979.             [String(event.target.options[event.target.selectedIndex].value)]);
  4980.       };
  4981.  
  4982.       // Languages section.
  4983.       $('language-button').onclick = function(event) {
  4984.         OptionsPage.navigateToPage('languages');
  4985.         chrome.send('coreOptionsUserMetricsAction',
  4986.             ['Options_LanuageAndSpellCheckSettings']);
  4987.       };
  4988.  
  4989.       // Downloads section.
  4990.       Preferences.getInstance().addEventListener('download.default_directory',
  4991.           this.onDefaultDownloadDirectoryChanged_.bind(this));
  4992.       $('downloadLocationChangeButton').onclick = function(event) {
  4993.         chrome.send('selectDownloadLocation');
  4994.       };
  4995.       if (!cr.isChromeOS) {
  4996.         $('autoOpenFileTypesResetToDefault').onclick = function(event) {
  4997.           chrome.send('autoOpenFileTypesAction');
  4998.         };
  4999.       }
  5000.  
  5001.       // HTTPS/SSL section.
  5002.       if (cr.isWindows || cr.isMac) {
  5003.         $('certificatesManageButton').onclick = function(event) {
  5004.           chrome.send('showManageSSLCertificates');
  5005.         };
  5006.       } else {
  5007.         $('certificatesManageButton').onclick = function(event) {
  5008.           OptionsPage.navigateToPage('certificates');
  5009.           chrome.send('coreOptionsUserMetricsAction',
  5010.                       ['Options_ManageSSLCertificates']);
  5011.         };
  5012.       }
  5013.  
  5014.       // Cloud Print section.
  5015.       // 'cloudPrintProxyEnabled' is true for Chrome branded builds on
  5016.       // certain platforms, or could be enabled by a lab.
  5017.       if (!cr.isChromeOS) {
  5018.         $('cloudPrintConnectorSetupButton').onclick = function(event) {
  5019.           if ($('cloudPrintManageButton').style.display == 'none') {
  5020.             // Disable the button, set its text to the intermediate state.
  5021.             $('cloudPrintConnectorSetupButton').textContent =
  5022.               loadTimeData.getString('cloudPrintConnectorEnablingButton');
  5023.             $('cloudPrintConnectorSetupButton').disabled = true;
  5024.             chrome.send('showCloudPrintSetupDialog');
  5025.           } else {
  5026.             chrome.send('disableCloudPrintConnector');
  5027.           }
  5028.         };
  5029.       }
  5030.       $('cloudPrintManageButton').onclick = function(event) {
  5031.         chrome.send('showCloudPrintManagePage');
  5032.       };
  5033.  
  5034.       // Accessibility section (CrOS only).
  5035.       if (cr.isChromeOS) {
  5036.         $('accessibility-spoken-feedback-check').onchange = function(event) {
  5037.           chrome.send('spokenFeedbackChange',
  5038.                       [$('accessibility-spoken-feedback-check').checked]);
  5039.         };
  5040.  
  5041.         $('accessibility-high-contrast-check').onchange = function(event) {
  5042.           chrome.send('highContrastChange',
  5043.                       [$('accessibility-high-contrast-check').checked]);
  5044.         };
  5045.       }
  5046.  
  5047.       // Display management section (CrOS only).
  5048.       if (cr.isChromeOS) {
  5049.         $('display-options').onclick = function(event) {
  5050.           OptionsPage.navigateToPage('display');
  5051.           chrome.send('coreOptionsUserMetricsAction',
  5052.                       ['Options_Display']);
  5053.         };
  5054.       }
  5055.  
  5056.       // Factory reset section (CrOS only).
  5057.       if (cr.isChromeOS) {
  5058.         $('factory-reset-restart').onclick = function(event) {
  5059.           OptionsPage.navigateToPage('factoryResetData');
  5060.         };
  5061.       }
  5062.     },
  5063.  
  5064.     /** @override */
  5065.     didShowPage: function() {
  5066.       $('search-field').focus();
  5067.     },
  5068.  
  5069.    /**
  5070.     * Called after all C++ UI handlers have called InitializePage to notify
  5071.     * that initialization is complete.
  5072.     * @private
  5073.     */
  5074.     notifyInitializationComplete_: function() {
  5075.       this.initializationComplete_ = true;
  5076.       cr.dispatchSimpleEvent(document, 'initializationComplete');
  5077.     },
  5078.  
  5079.     /**
  5080.      * Event listener for the 'session.restore_on_startup' pref.
  5081.      * @param {Event} event The preference change event.
  5082.      * @private
  5083.      */
  5084.     onRestoreOnStartupChanged_: function(event) {
  5085.       /** @const */ var showHomePageValue = 0;
  5086.  
  5087.       if (event.value.value == showHomePageValue) {
  5088.         // If the user previously selected "Show the homepage", the
  5089.         // preference will already be migrated to "Open a specific page". So
  5090.         // the only way to reach this code is if the 'restore on startup'
  5091.         // preference is managed.
  5092.         assert(event.value.controlledBy);
  5093.  
  5094.         // Select "open the following pages" and lock down the list of URLs
  5095.         // to reflect the intention of the policy.
  5096.         $('startup-show-pages').checked = true;
  5097.         StartupOverlay.getInstance().setControlsDisabled(true);
  5098.       } else {
  5099.         // Re-enable the controls in the startup overlay if necessary.
  5100.         StartupOverlay.getInstance().updateControlStates();
  5101.       }
  5102.     },
  5103.  
  5104.     /**
  5105.      * Handler for messages sent from the main uber page.
  5106.      * @param {Event} e The 'message' event from the uber page.
  5107.      * @private
  5108.      */
  5109.     handleWindowMessage_: function(e) {
  5110.       if (e.data.method == 'frameSelected')
  5111.         $('search-field').focus();
  5112.     },
  5113.  
  5114.     /**
  5115.      * Shows the given section.
  5116.      * @param {HTMLElement} section The section to be shown.
  5117.      * @param {HTMLElement} container The container for the section. Must be
  5118.      *     inside of |section|.
  5119.      * @param {boolean} animate Indicate if the expansion should be animated.
  5120.      * @private
  5121.      */
  5122.     showSection_: function(section, container, animate) {
  5123.       if (animate)
  5124.         this.addTransitionEndListener_(section);
  5125.  
  5126.       // Unhide
  5127.       section.hidden = false;
  5128.  
  5129.       var expander = function() {
  5130.         // Reveal the section using a WebKit transition if animating.
  5131.         if (animate) {
  5132.           section.classList.add('sliding');
  5133.           section.style.height = container.offsetHeight + 'px';
  5134.         } else {
  5135.           section.style.height = 'auto';
  5136.         }
  5137.         // Force an update of the list of paired Bluetooth devices.
  5138.         if (cr.isChromeOS)
  5139.           $('bluetooth-paired-devices-list').refresh();
  5140.       };
  5141.  
  5142.       // Delay starting the transition if animating so that hidden change will
  5143.       // be processed.
  5144.       if (animate)
  5145.         setTimeout(expander, 0);
  5146.       else
  5147.         expander();
  5148.       },
  5149.  
  5150.     /**
  5151.      * Shows the given section, with animation.
  5152.      * @param {HTMLElement} section The section to be shown.
  5153.      * @param {HTMLElement} container The container for the section. Must be
  5154.      *     inside of |section|.
  5155.      * @private
  5156.      */
  5157.     showSectionWithAnimation_: function(section, container) {
  5158.       this.showSection_(section, container, /*animate */ true);
  5159.     },
  5160.  
  5161.     /**
  5162.      * See showSectionWithAnimation_.
  5163.      */
  5164.     hideSectionWithAnimation_: function(section, container) {
  5165.       this.addTransitionEndListener_(section);
  5166.  
  5167.       // Before we start hiding the section, we need to set
  5168.       // the height to a pixel value.
  5169.       section.style.height = container.offsetHeight + 'px';
  5170.  
  5171.       // Delay starting the transition so that the height change will be
  5172.       // processed.
  5173.       setTimeout(function() {
  5174.         // Hide the section using a WebKit transition.
  5175.         section.classList.add('sliding');
  5176.         section.style.height = '';
  5177.       }, 0);
  5178.     },
  5179.  
  5180.     /**
  5181.      * See showSectionWithAnimation_.
  5182.      */
  5183.     toggleSectionWithAnimation_: function(section, container) {
  5184.       if (section.style.height == '')
  5185.         this.showSectionWithAnimation_(section, container);
  5186.       else
  5187.         this.hideSectionWithAnimation_(section, container);
  5188.     },
  5189.  
  5190.     /**
  5191.      * Scrolls the settings page to make the section visible auto-expanding
  5192.      * advanced settings if required.  The transition is not animated.  This
  5193.      * method is used to ensure that a section associated with an overlay
  5194.      * is visible when the overlay is closed.
  5195.      * @param {!Element} section  The section to make visible.
  5196.      * @private
  5197.      */
  5198.     scrollToSection_: function(section) {
  5199.       var advancedSettings = $('advanced-settings');
  5200.       var container = $('advanced-settings-container');
  5201.       if (advancedSettings.hidden && section.parentNode == container) {
  5202.         this.showSection_($('advanced-settings'),
  5203.                           $('advanced-settings-container'),
  5204.                           /* animate */ false);
  5205.         this.updateAdvancedSettingsExpander_();
  5206.       }
  5207.  
  5208.       if (!this.initializationComplete_) {
  5209.         var self = this;
  5210.         var callback = function() {
  5211.            document.removeEventListener('initializationComplete', callback);
  5212.            self.scrollToSection_(section);
  5213.         };
  5214.         document.addEventListener('initializationComplete', callback);
  5215.         return;
  5216.       }
  5217.  
  5218.       var pageContainer = $('page-container');
  5219.       var pageTop = parseFloat(pageContainer.style.top);
  5220.       var topSection = document.querySelector('#page-container section');
  5221.       var pageHeight = document.body.scrollHeight - topSection.offsetTop;
  5222.       var sectionTop = section.offsetTop;
  5223.       var sectionHeight = section.offsetHeight;
  5224.       var marginBottom = window.getComputedStyle(section).marginBottom;
  5225.       if (marginBottom)
  5226.         sectionHeight += parseFloat(marginBottom);
  5227.       if (pageHeight - pageTop < sectionTop + sectionHeight) {
  5228.         pageContainer.oldScrollTop = sectionTop + sectionHeight - pageHeight;
  5229.         var verticalPosition = pageContainer.getBoundingClientRect().top -
  5230.             pageContainer.oldScrollTop;
  5231.         pageContainer.style.top = verticalPosition + 'px';
  5232.       }
  5233.     },
  5234.  
  5235.     /**
  5236.      * Adds a |webkitTransitionEnd| listener to the given section so that
  5237.      * it can be animated. The listener will only be added to a given section
  5238.      * once, so this can be called as multiple times.
  5239.      * @param {HTMLElement} section The section to be animated.
  5240.      * @private
  5241.      */
  5242.     addTransitionEndListener_: function(section) {
  5243.       if (section.hasTransitionEndListener_)
  5244.         return;
  5245.  
  5246.       section.addEventListener('webkitTransitionEnd',
  5247.           this.onTransitionEnd_.bind(this));
  5248.       section.hasTransitionEndListener_ = true;
  5249.     },
  5250.  
  5251.     /**
  5252.      * Called after an animation transition has ended.
  5253.      * @private
  5254.      */
  5255.     onTransitionEnd_: function(event) {
  5256.       if (event.propertyName != 'height')
  5257.         return;
  5258.  
  5259.       var section = event.target;
  5260.  
  5261.       // Disable WebKit transitions.
  5262.       section.classList.remove('sliding');
  5263.  
  5264.       if (section.style.height == '') {
  5265.         // Hide the content so it can't get tab focus.
  5266.         section.hidden = true;
  5267.       } else {
  5268.         // Set the section height to 'auto' to allow for size changes
  5269.         // (due to font change or dynamic content).
  5270.         section.style.height = 'auto';
  5271.       }
  5272.     },
  5273.  
  5274.     updateAdvancedSettingsExpander_: function() {
  5275.       var expander = $('advanced-settings-expander');
  5276.       if ($('advanced-settings').style.height == '')
  5277.         expander.textContent = loadTimeData.getString('showAdvancedSettings');
  5278.       else
  5279.         expander.textContent = loadTimeData.getString('hideAdvancedSettings');
  5280.     },
  5281.  
  5282.     /**
  5283.      * Updates the sync section with the given state.
  5284.      * @param {Object} syncData A bunch of data records that describe the status
  5285.      *     of the sync system.
  5286.      * @private
  5287.      */
  5288.     updateSyncState_: function(syncData) {
  5289.       if (!syncData.syncSystemEnabled) {
  5290.         $('sync-section').hidden = true;
  5291.         return;
  5292.       }
  5293.  
  5294.       $('sync-section').hidden = false;
  5295.       this.syncSetupCompleted = syncData.setupCompleted;
  5296.       $('customize-sync').hidden = !syncData.setupCompleted;
  5297.  
  5298.       var startStopButton = $('start-stop-sync');
  5299.       startStopButton.disabled = syncData.managed ||
  5300.           syncData.setupInProgress;
  5301.       startStopButton.hidden =
  5302.           syncData.setupCompleted && cr.isChromeOS;
  5303.       startStopButton.textContent =
  5304.           syncData.setupCompleted ?
  5305.               loadTimeData.getString('syncButtonTextStop') :
  5306.           syncData.setupInProgress ?
  5307.               loadTimeData.getString('syncButtonTextInProgress') :
  5308.               loadTimeData.getString('syncButtonTextStart');
  5309.       $('start-stop-sync-indicator').hidden = startStopButton.hidden;
  5310.  
  5311.       // TODO(estade): can this just be textContent?
  5312.       $('sync-status-text').innerHTML = syncData.statusText;
  5313.       var statusSet = syncData.statusText.length != 0;
  5314.       $('sync-overview').hidden = statusSet;
  5315.       $('sync-status').hidden = !statusSet;
  5316.  
  5317.       $('sync-action-link').textContent = syncData.actionLinkText;
  5318.       $('sync-action-link').hidden = syncData.actionLinkText.length == 0;
  5319.       $('sync-action-link').disabled = syncData.managed;
  5320.  
  5321.       // On Chrome OS, sign out the user and sign in again to get fresh
  5322.       // credentials on auth errors.
  5323.       $('sync-action-link').onclick = function(event) {
  5324.         if (cr.isChromeOS && syncData.hasError)
  5325.           SyncSetupOverlay.doSignOutOnAuthError();
  5326.         else
  5327.           SyncSetupOverlay.showErrorUI();
  5328.       };
  5329.  
  5330.       if (syncData.hasError)
  5331.         $('sync-status').classList.add('sync-error');
  5332.       else
  5333.         $('sync-status').classList.remove('sync-error');
  5334.  
  5335.       $('customize-sync').disabled = syncData.hasUnrecoverableError;
  5336.       // Move #enable-auto-login-checkbox to a different location on CrOS.
  5337.       if (cr.isChromeOs) {
  5338.         $('sync-general').insertBefore($('sync-status').nextSibling,
  5339.                                        $('enable-auto-login-checkbox'));
  5340.       }
  5341.       $('enable-auto-login-checkbox').hidden = !syncData.autoLoginVisible;
  5342.     },
  5343.  
  5344.     /**
  5345.      * Get the start/stop sync button DOM element. Used for testing.
  5346.      * @return {DOMElement} The start/stop sync button.
  5347.      * @private
  5348.      */
  5349.     getStartStopSyncButton_: function() {
  5350.       return $('start-stop-sync');
  5351.     },
  5352.  
  5353.     /**
  5354.      * Event listener for the 'show home button' preference. Shows/hides the
  5355.      * UI for changing the home page with animation, unless this is the first
  5356.      * time this function is called, in which case there is no animation.
  5357.      * @param {Event} event The preference change event.
  5358.      */
  5359.     onShowHomeButtonChanged_: function(event) {
  5360.       var section = $('change-home-page-section');
  5361.       if (this.onShowHomeButtonChangedCalled_) {
  5362.         var container = $('change-home-page-section-container');
  5363.         if (event.value.value)
  5364.           this.showSectionWithAnimation_(section, container);
  5365.         else
  5366.           this.hideSectionWithAnimation_(section, container);
  5367.       } else {
  5368.         section.hidden = !event.value.value;
  5369.         this.onShowHomeButtonChangedCalled_ = true;
  5370.       }
  5371.     },
  5372.  
  5373.     /**
  5374.      * Event listener for the 'homepage is NTP' preference. Updates the label
  5375.      * next to the 'Change' button.
  5376.      * @param {Event} event The preference change event.
  5377.      */
  5378.     onHomePageIsNtpChanged_: function(event) {
  5379.       if (!event.value.uncommitted) {
  5380.         $('home-page-url').hidden = event.value.value;
  5381.         $('home-page-ntp').hidden = !event.value.value;
  5382.       }
  5383.     },
  5384.  
  5385.     /**
  5386.      * Event listener for changes to the homepage preference. Updates the label
  5387.      * next to the 'Change' button.
  5388.      * @param {Event} event The preference change event.
  5389.      */
  5390.     onHomePageChanged_: function(event) {
  5391.       if (!event.value.uncommitted)
  5392.         $('home-page-url').textContent = this.stripHttp_(event.value.value);
  5393.     },
  5394.  
  5395.     /**
  5396.      * Removes the 'http://' from a URL, like the omnibox does. If the string
  5397.      * doesn't start with 'http://' it is returned unchanged.
  5398.      * @param {string} url The url to be processed
  5399.      * @return {string} The url with the 'http://' removed.
  5400.      */
  5401.     stripHttp_: function(url) {
  5402.       return url.replace(/^http:\/\//, '');
  5403.     },
  5404.  
  5405.    /**
  5406.     * Shows the autoLaunch preference and initializes its checkbox value.
  5407.     * @param {bool} enabled Whether autolaunch is enabled or or not.
  5408.     * @private
  5409.     */
  5410.     updateAutoLaunchState_: function(enabled) {
  5411.       $('auto-launch-option').hidden = false;
  5412.       $('auto-launch').checked = enabled;
  5413.     },
  5414.  
  5415.     /**
  5416.      * Called when the value of the download.default_directory preference
  5417.      * changes.
  5418.      * @param {Event} event Change event.
  5419.      * @private
  5420.      */
  5421.     onDefaultDownloadDirectoryChanged_: function(event) {
  5422.       $('downloadLocationPath').value = event.value.value;
  5423.       if (cr.isChromeOS) {
  5424.         // On ChromeOS, replace /special/drive with Drive for drive paths, and
  5425.         // /home/chronos/user/Downloads with Downloads for local files.
  5426.         // Also replace '/' with ' \u203a ' (angled quote sign) everywhere.
  5427.         var path = $('downloadLocationPath').value;
  5428.         path = path.replace(/^\/special\/drive/, 'Google Drive');
  5429.         path = path.replace(/^\/home\/chronos\/user\//, '');
  5430.         path = path.replace(/\//g, ' \u203a ');
  5431.         $('downloadLocationPath').value = path;
  5432.       }
  5433.       if (event.value.disabled)
  5434.         $('download-location-label').classList.add('disabled');
  5435.       else
  5436.         $('download-location-label').classList.remove('disabled');
  5437.       $('downloadLocationChangeButton').disabled = event.value.disabled;
  5438.     },
  5439.  
  5440.     /**
  5441.      * Update the Default Browsers section based on the current state.
  5442.      * @param {string} statusString Description of the current default state.
  5443.      * @param {boolean} isDefault Whether or not the browser is currently
  5444.      *     default.
  5445.      * @param {boolean} canBeDefault Whether or not the browser can be default.
  5446.      * @private
  5447.      */
  5448.     updateDefaultBrowserState_: function(statusString, isDefault,
  5449.                                          canBeDefault) {
  5450.       if (!cr.isChromeOS) {
  5451.         var label = $('default-browser-state');
  5452.         label.textContent = statusString;
  5453.  
  5454.         $('set-as-default-browser').hidden = !canBeDefault || isDefault;
  5455.       }
  5456.     },
  5457.  
  5458.     /**
  5459.      * Clears the search engine popup.
  5460.      * @private
  5461.      */
  5462.     clearSearchEngines_: function() {
  5463.       $('default-search-engine').textContent = '';
  5464.     },
  5465.  
  5466.     /**
  5467.      * Updates the search engine popup with the given entries.
  5468.      * @param {Array} engines List of available search engines.
  5469.      * @param {number} defaultValue The value of the current default engine.
  5470.      * @param {boolean} defaultManaged Whether the default search provider is
  5471.      *     managed. If true, the default search provider can't be changed.
  5472.      * @private
  5473.      */
  5474.     updateSearchEngines_: function(engines, defaultValue, defaultManaged) {
  5475.       this.clearSearchEngines_();
  5476.       engineSelect = $('default-search-engine');
  5477.       engineSelect.disabled = defaultManaged;
  5478.       if (defaultManaged && defaultValue == -1)
  5479.         return;
  5480.       engineCount = engines.length;
  5481.       var defaultIndex = -1;
  5482.       for (var i = 0; i < engineCount; i++) {
  5483.         var engine = engines[i];
  5484.         var option = new Option(engine.name, engine.index);
  5485.         if (defaultValue == option.value)
  5486.           defaultIndex = i;
  5487.         engineSelect.appendChild(option);
  5488.       }
  5489.       if (defaultIndex >= 0)
  5490.         engineSelect.selectedIndex = defaultIndex;
  5491.     },
  5492.  
  5493.     /**
  5494.      * Set the default search engine based on the popup selection.
  5495.      * @private
  5496.      */
  5497.     setDefaultSearchEngine_: function() {
  5498.       var engineSelect = $('default-search-engine');
  5499.       var selectedIndex = engineSelect.selectedIndex;
  5500.       if (selectedIndex >= 0) {
  5501.         var selection = engineSelect.options[selectedIndex];
  5502.         chrome.send('setDefaultSearchEngine', [String(selection.value)]);
  5503.       }
  5504.     },
  5505.  
  5506.    /**
  5507.      * Sets or clear whether Chrome should Auto-launch on computer startup.
  5508.      * @private
  5509.      */
  5510.     handleAutoLaunchChanged_: function() {
  5511.       chrome.send('toggleAutoLaunch', [$('auto-launch').checked]);
  5512.     },
  5513.  
  5514.     /**
  5515.      * Get the selected profile item from the profile list. This also works
  5516.      * correctly if the list is not displayed.
  5517.      * @return {Object} the profile item object, or null if nothing is selected.
  5518.      * @private
  5519.      */
  5520.     getSelectedProfileItem_: function() {
  5521.       var profilesList = $('profiles-list');
  5522.       if (profilesList.hidden) {
  5523.         if (profilesList.dataModel.length > 0)
  5524.           return profilesList.dataModel.item(0);
  5525.       } else {
  5526.         return profilesList.selectedItem;
  5527.       }
  5528.       return null;
  5529.     },
  5530.  
  5531.     /**
  5532.      * Helper function to set the status of profile view buttons to disabled or
  5533.      * enabled, depending on the number of profiles and selection status of the
  5534.      * profiles list.
  5535.      * @private
  5536.      */
  5537.     setProfileViewButtonsStatus_: function() {
  5538.       var profilesList = $('profiles-list');
  5539.       var selectedProfile = profilesList.selectedItem;
  5540.       var hasSelection = selectedProfile != null;
  5541.       var hasSingleProfile = profilesList.dataModel.length == 1;
  5542.       $('profiles-manage').disabled = !hasSelection ||
  5543.           !selectedProfile.isCurrentProfile;
  5544.       if (hasSelection && !selectedProfile.isCurrentProfile)
  5545.         $('profiles-manage').title = loadTimeData.getString('currentUserOnly');
  5546.       else
  5547.         $('profiles-manage').title = '';
  5548.       $('profiles-delete').disabled = !hasSelection && !hasSingleProfile;
  5549.       var importData = $('import-data');
  5550.       if (importData) {
  5551.         importData.disabled = $('import-data').disabled = hasSelection &&
  5552.           !selectedProfile.isCurrentProfile;
  5553.       }
  5554.     },
  5555.  
  5556.     /**
  5557.      * Display the correct dialog layout, depending on how many profiles are
  5558.      * available.
  5559.      * @param {number} numProfiles The number of profiles to display.
  5560.      * @private
  5561.      */
  5562.     setProfileViewSingle_: function(numProfiles) {
  5563.       var hasSingleProfile = numProfiles == 1;
  5564.       $('profiles-list').hidden = hasSingleProfile;
  5565.       $('profiles-single-message').hidden = !hasSingleProfile;
  5566.       $('profiles-manage').hidden = hasSingleProfile;
  5567.       $('profiles-delete').textContent = hasSingleProfile ?
  5568.           loadTimeData.getString('profilesDeleteSingle') :
  5569.           loadTimeData.getString('profilesDelete');
  5570.     },
  5571.  
  5572.     /**
  5573.      * Adds all |profiles| to the list.
  5574.      * @param {Array.<Object>} profiles An array of profile info objects.
  5575.      *     each object is of the form:
  5576.      *       profileInfo = {
  5577.      *         name: "Profile Name",
  5578.      *         iconURL: "chrome://path/to/icon/image",
  5579.      *         filePath: "/path/to/profile/data/on/disk",
  5580.      *         isCurrentProfile: false
  5581.      *       };
  5582.      * @private
  5583.      */
  5584.     setProfilesInfo_: function(profiles) {
  5585.       this.setProfileViewSingle_(profiles.length);
  5586.       // add it to the list, even if the list is hidden so we can access it
  5587.       // later.
  5588.       $('profiles-list').dataModel = new ArrayDataModel(profiles);
  5589.  
  5590.       // Received new data. If showing the "manage" overlay, keep it up to
  5591.       // date. If showing the "delete" overlay, close it.
  5592.       if (ManageProfileOverlay.getInstance().visible &&
  5593.           !$('manage-profile-overlay-manage').hidden) {
  5594.         ManageProfileOverlay.showManageDialog();
  5595.       } else {
  5596.         ManageProfileOverlay.getInstance().visible = false;
  5597.       }
  5598.  
  5599.       this.setProfileViewButtonsStatus_();
  5600.     },
  5601.  
  5602.     /**
  5603.      * Returns the currently active profile for this browser window.
  5604.      * @return {Object} A profile info object.
  5605.      * @private
  5606.      */
  5607.     getCurrentProfile_: function() {
  5608.       for (var i = 0; i < $('profiles-list').dataModel.length; i++) {
  5609.         var profile = $('profiles-list').dataModel.item(i);
  5610.         if (profile.isCurrentProfile)
  5611.           return profile;
  5612.       }
  5613.  
  5614.       assert(false,
  5615.              'There should always be a current profile, but none found.');
  5616.     },
  5617.  
  5618.     setGtkThemeButtonEnabled_: function(enabled) {
  5619.       if (!cr.isChromeOS && navigator.platform.match(/linux|BSD/i))
  5620.         $('themes-GTK-button').disabled = !enabled;
  5621.     },
  5622.  
  5623.     setThemesResetButtonEnabled_: function(enabled) {
  5624.       $('themes-reset').disabled = !enabled;
  5625.     },
  5626.  
  5627.     /**
  5628.      * (Re)loads IMG element with current user account picture.
  5629.      * @private
  5630.      */
  5631.     updateAccountPicture_: function() {
  5632.       var picture = $('account-picture');
  5633.       if (picture) {
  5634.         picture.src = 'chrome://userimage/' + this.username_ + '?id=' +
  5635.             Date.now();
  5636.       }
  5637.     },
  5638.  
  5639.     /**
  5640.      * Handle the 'add device' button click.
  5641.      * @private
  5642.      */
  5643.     handleAddBluetoothDevice_: function() {
  5644.       $('bluetooth-unpaired-devices-list').clear();
  5645.       chrome.send('findBluetoothDevices');
  5646.       OptionsPage.showPageByName('bluetooth', false);
  5647.     },
  5648.  
  5649.     /**
  5650.      * Enables factory reset section.
  5651.      * @private
  5652.      */
  5653.     enableFactoryResetSection_: function() {
  5654.       $('factory-reset-section').hidden = false;
  5655.     },
  5656.  
  5657.     /**
  5658.      * Set the checked state of the metrics reporting checkbox.
  5659.      * @private
  5660.      */
  5661.     setMetricsReportingCheckboxState_: function(checked, disabled) {
  5662.       $('metricsReportingEnabled').checked = checked;
  5663.       $('metricsReportingEnabled').disabled = disabled;
  5664.     },
  5665.  
  5666.     /**
  5667.      * @private
  5668.      */
  5669.     setMetricsReportingSettingVisibility_: function(visible) {
  5670.       if (visible)
  5671.         $('metricsReportingSetting').style.display = 'block';
  5672.       else
  5673.         $('metricsReportingSetting').style.display = 'none';
  5674.     },
  5675.  
  5676.     /**
  5677.      * Set the visibility of the password generation checkbox.
  5678.      * @private
  5679.      */
  5680.     setPasswordGenerationSettingVisibility_: function(visible) {
  5681.       if (visible)
  5682.         $('password-generation-checkbox').style.display = 'block';
  5683.       else
  5684.         $('password-generation-checkbox').style.display = 'none';
  5685.     },
  5686.  
  5687.     /**
  5688.      * Set the font size selected item.
  5689.      * @private
  5690.      */
  5691.     setFontSize_: function(font_size_value) {
  5692.       var selectCtl = $('defaultFontSize');
  5693.       for (var i = 0; i < selectCtl.options.length; i++) {
  5694.         if (selectCtl.options[i].value == font_size_value) {
  5695.           selectCtl.selectedIndex = i;
  5696.           if ($('Custom'))
  5697.             selectCtl.remove($('Custom').index);
  5698.           return;
  5699.         }
  5700.       }
  5701.  
  5702.       // Add/Select Custom Option in the font size label list.
  5703.       if (!$('Custom')) {
  5704.         var option = new Option(loadTimeData.getString('fontSizeLabelCustom'),
  5705.                                 -1, false, true);
  5706.         option.setAttribute('id', 'Custom');
  5707.         selectCtl.add(option);
  5708.       }
  5709.       $('Custom').selected = true;
  5710.     },
  5711.  
  5712.     /**
  5713.      * Populate the page zoom selector with values received from the caller.
  5714.      * @param {Array} items An array of items to populate the selector.
  5715.      *     each object is an array with three elements as follows:
  5716.      *       0: The title of the item (string).
  5717.      *       1: The value of the item (number).
  5718.      *       2: Whether the item should be selected (boolean).
  5719.      * @private
  5720.      */
  5721.     setupPageZoomSelector_: function(items) {
  5722.       var element = $('defaultZoomFactor');
  5723.  
  5724.       // Remove any existing content.
  5725.       element.textContent = '';
  5726.  
  5727.       // Insert new child nodes into select element.
  5728.       var value, title, selected;
  5729.       for (var i = 0; i < items.length; i++) {
  5730.         title = items[i][0];
  5731.         value = items[i][1];
  5732.         selected = items[i][2];
  5733.         element.appendChild(new Option(title, value, false, selected));
  5734.       }
  5735.     },
  5736.  
  5737.     /**
  5738.      * Shows/hides the autoOpenFileTypesResetToDefault button and label, with
  5739.      * animation.
  5740.      * @param {boolean} display Whether to show the button and label or not.
  5741.      * @private
  5742.      */
  5743.     setAutoOpenFileTypesDisplayed_: function(display) {
  5744.       if (cr.isChromeOS)
  5745.         return;
  5746.  
  5747.       if ($('advanced-settings').hidden) {
  5748.         // If the Advanced section is hidden, don't animate the transition.
  5749.         $('auto-open-file-types-section').hidden = !display;
  5750.       } else {
  5751.         if (display) {
  5752.           this.showSectionWithAnimation_(
  5753.               $('auto-open-file-types-section'),
  5754.               $('auto-open-file-types-container'));
  5755.         } else {
  5756.           this.hideSectionWithAnimation_(
  5757.               $('auto-open-file-types-section'),
  5758.               $('auto-open-file-types-container'));
  5759.         }
  5760.       }
  5761.     },
  5762.  
  5763.     /**
  5764.      * Set the enabled state for the proxy settings button.
  5765.      * @private
  5766.      */
  5767.     setupProxySettingsSection_: function(disabled, label) {
  5768.       if (!cr.isChromeOS) {
  5769.         $('proxiesConfigureButton').disabled = disabled;
  5770.         $('proxiesLabel').textContent = label;
  5771.       }
  5772.     },
  5773.  
  5774.     /**
  5775.      * Set the Cloud Print proxy UI to enabled, disabled, or processing.
  5776.      * @private
  5777.      */
  5778.     setupCloudPrintConnectorSection_: function(disabled, label, allowed) {
  5779.       if (!cr.isChromeOS) {
  5780.         $('cloudPrintConnectorLabel').textContent = label;
  5781.         if (disabled || !allowed) {
  5782.           $('cloudPrintConnectorSetupButton').textContent =
  5783.             loadTimeData.getString('cloudPrintConnectorDisabledButton');
  5784.           $('cloudPrintManageButton').style.display = 'none';
  5785.         } else {
  5786.           $('cloudPrintConnectorSetupButton').textContent =
  5787.             loadTimeData.getString('cloudPrintConnectorEnabledButton');
  5788.           $('cloudPrintManageButton').style.display = 'inline';
  5789.         }
  5790.         $('cloudPrintConnectorSetupButton').disabled = !allowed;
  5791.       }
  5792.     },
  5793.  
  5794.     /**
  5795.      * @private
  5796.      */
  5797.     removeCloudPrintConnectorSection_: function() {
  5798.      if (!cr.isChromeOS) {
  5799.         var connectorSectionElm = $('cloud-print-connector-section');
  5800.         if (connectorSectionElm)
  5801.           connectorSectionElm.parentNode.removeChild(connectorSectionElm);
  5802.       }
  5803.     },
  5804.  
  5805.     /**
  5806.      * Set the initial state of the spoken feedback checkbox.
  5807.      * @private
  5808.      */
  5809.     setSpokenFeedbackCheckboxState_: function(checked) {
  5810.       $('accessibility-spoken-feedback-check').checked = checked;
  5811.     },
  5812.  
  5813.     /**
  5814.      * Set the initial state of the high contrast checkbox.
  5815.      * @private
  5816.      */
  5817.     setHighContrastCheckboxState_: function(checked) {
  5818.       $('accessibility-high-contrast-check').checked = checked;
  5819.     },
  5820.  
  5821.     /**
  5822.      * Set the initial state of the virtual keyboard checkbox.
  5823.      * @private
  5824.      */
  5825.     setVirtualKeyboardCheckboxState_: function(checked) {
  5826.       // TODO(zork): Update UI
  5827.     },
  5828.  
  5829.     /**
  5830.      * Show/hide mouse settings slider.
  5831.      * @private
  5832.      */
  5833.     showMouseControls_: function(show) {
  5834.       $('mouse-settings').hidden = !show;
  5835.     },
  5836.  
  5837.     /**
  5838.      * Show/hide touchpad-related settings.
  5839.      * @private
  5840.      */
  5841.     showTouchpadControls_: function(show) {
  5842.       $('touchpad-settings').hidden = !show;
  5843.       $('accessibility-tap-dragging').hidden = !show;
  5844.     },
  5845.  
  5846.     /**
  5847.      * Show/hide the display options button on the System settings page.
  5848.      * @private
  5849.      */
  5850.     showDisplayOptions_: function(show) {
  5851.       $('display-options-section').hidden = !show;
  5852.     },
  5853.  
  5854.     /**
  5855.      * Activate the Bluetooth settings section on the System settings page.
  5856.      * @private
  5857.      */
  5858.     showBluetoothSettings_: function() {
  5859.       $('bluetooth-devices').hidden = false;
  5860.     },
  5861.  
  5862.     /**
  5863.      * Dectivates the Bluetooth settings section from the System settings page.
  5864.      * @private
  5865.      */
  5866.     hideBluetoothSettings_: function() {
  5867.       $('bluetooth-devices').hidden = true;
  5868.     },
  5869.  
  5870.     /**
  5871.      * Sets the state of the checkbox indicating if Bluetooth is turned on. The
  5872.      * state of the "Find devices" button and the list of discovered devices may
  5873.      * also be affected by a change to the state.
  5874.      * @param {boolean} checked Flag Indicating if Bluetooth is turned on.
  5875.      * @private
  5876.      */
  5877.     setBluetoothState_: function(checked) {
  5878.       $('enable-bluetooth').checked = checked;
  5879.       $('bluetooth-paired-devices-list').parentNode.hidden = !checked;
  5880.       $('bluetooth-add-device').hidden = !checked;
  5881.       $('bluetooth-reconnect-device').hidden = !checked;
  5882.       // Flush list of previously discovered devices if bluetooth is turned off.
  5883.       if (!checked) {
  5884.         $('bluetooth-paired-devices-list').clear();
  5885.         $('bluetooth-unpaired-devices-list').clear();
  5886.       } else {
  5887.         chrome.send('getPairedBluetoothDevices');
  5888.       }
  5889.     },
  5890.  
  5891.     /**
  5892.      * Adds an element to the list of available Bluetooth devices. If an element
  5893.      * with a matching address is found, the existing element is updated.
  5894.      * @param {{name: string,
  5895.      *          address: string,
  5896.      *          paired: boolean,
  5897.      *          bonded: boolean,
  5898.      *          connected: boolean}} device
  5899.      *     Decription of the Bluetooth device.
  5900.      * @private
  5901.      */
  5902.     addBluetoothDevice_: function(device) {
  5903.       var list = $('bluetooth-unpaired-devices-list');
  5904.       if (device.paired) {
  5905.         // Test to see if the device is currently in the unpaired list, in which
  5906.         // case it should be removed from that list.
  5907.         var index = $('bluetooth-unpaired-devices-list').find(device.address);
  5908.         if (index != undefined)
  5909.           $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
  5910.         list = $('bluetooth-paired-devices-list');
  5911.       } else {
  5912.         // Test to see if the device is currently in the paired list, in which
  5913.         // case it should be removed from that list.
  5914.         var index = $('bluetooth-paired-devices-list').find(device.address);
  5915.         if (index != undefined)
  5916.           $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
  5917.       }
  5918.       list.appendDevice(device);
  5919.  
  5920.       // One device can be in the process of pairing.  If found, display
  5921.       // the Bluetooth pairing overlay.
  5922.       if (device.pairing)
  5923.         BluetoothPairing.showDialog(device);
  5924.     },
  5925.  
  5926.     /**
  5927.      * Removes an element from the list of available devices.
  5928.      * @param {string} address Unique address of the device.
  5929.      * @private
  5930.      */
  5931.     removeBluetoothDevice_: function(address) {
  5932.       var index = $('bluetooth-unpaired-devices-list').find(address);
  5933.       if (index != undefined) {
  5934.         $('bluetooth-unpaired-devices-list').deleteItemAtIndex(index);
  5935.       } else {
  5936.         index = $('bluetooth-paired-devices-list').find(address);
  5937.         if (index != undefined)
  5938.           $('bluetooth-paired-devices-list').deleteItemAtIndex(index);
  5939.       }
  5940.     }
  5941.  
  5942.   };
  5943.  
  5944.   //Forward public APIs to private implementations.
  5945.   [
  5946.     'addBluetoothDevice',
  5947.     'enableFactoryResetSection',
  5948.     'getCurrentProfile',
  5949.     'getStartStopSyncButton',
  5950.     'hideBluetoothSettings',
  5951.     'notifyInitializationComplete',
  5952.     'removeBluetoothDevice',
  5953.     'removeCloudPrintConnectorSection',
  5954.     'scrollToSection',
  5955.     'setAutoOpenFileTypesDisplayed',
  5956.     'setBluetoothState',
  5957.     'setFontSize',
  5958.     'setGtkThemeButtonEnabled',
  5959.     'setHighContrastCheckboxState',
  5960.     'setMetricsReportingCheckboxState',
  5961.     'setMetricsReportingSettingVisibility',
  5962.     'setPasswordGenerationSettingVisibility',
  5963.     'setProfilesInfo',
  5964.     'setSpokenFeedbackCheckboxState',
  5965.     'setThemesResetButtonEnabled',
  5966.     'setVirtualKeyboardCheckboxState',
  5967.     'setupCloudPrintConnectorSection',
  5968.     'setupPageZoomSelector',
  5969.     'setupProxySettingsSection',
  5970.     'showBluetoothSettings',
  5971.     'showDisplayOptions',
  5972.     'showMouseControls',
  5973.     'showTouchpadControls',
  5974.     'updateAccountPicture',
  5975.     'updateAutoLaunchState',
  5976.     'updateDefaultBrowserState',
  5977.     'updateSearchEngines',
  5978.     'updateStartupPages',
  5979.     'updateSyncState',
  5980.   ].forEach(function(name) {
  5981.     BrowserOptions[name] = function() {
  5982.       var instance = BrowserOptions.getInstance();
  5983.       return instance[name + '_'].apply(instance, arguments);
  5984.     };
  5985.   });
  5986.  
  5987.   if (cr.isChromeOS) {
  5988.     /**
  5989.      * Returns username (canonical email) of the user logged in (ChromeOS only).
  5990.      * @return {string} user email.
  5991.      */
  5992.     // TODO(jhawkins): Investigate the use case for this method.
  5993.     BrowserOptions.getLoggedInUsername = function() {
  5994.       return BrowserOptions.getInstance().username_;
  5995.     };
  5996.   }
  5997.  
  5998.   // Export
  5999.   return {
  6000.     BrowserOptions: BrowserOptions
  6001.   };
  6002. });
  6003.  
  6004. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6005. // Use of this source code is governed by a BSD-style license that can be
  6006. // found in the LICENSE file.
  6007.  
  6008. cr.define('options.browser_options', function() {
  6009.   /** @const */ var DeletableItem = options.DeletableItem;
  6010.   /** @const */ var DeletableItemList = options.DeletableItemList;
  6011.   /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  6012.  
  6013.   /**
  6014.    * Creates a new profile list item.
  6015.    * @param {Object} profileInfo The profile this item respresents.
  6016.    * @constructor
  6017.    * @extends {cr.ui.DeletableItem}
  6018.    */
  6019.   function ProfileListItem(profileInfo) {
  6020.     var el = cr.doc.createElement('div');
  6021.     el.profileInfo_ = profileInfo;
  6022.     ProfileListItem.decorate(el);
  6023.     return el;
  6024.   }
  6025.  
  6026.   /**
  6027.    * Decorates an element as a profile list item.
  6028.    * @param {!HTMLElement} el The element to decorate.
  6029.    */
  6030.   ProfileListItem.decorate = function(el) {
  6031.     el.__proto__ = ProfileListItem.prototype;
  6032.     el.decorate();
  6033.   };
  6034.  
  6035.   ProfileListItem.prototype = {
  6036.     __proto__: DeletableItem.prototype,
  6037.  
  6038.     /**
  6039.      * @type {string} the file path of this profile list item.
  6040.      */
  6041.     get profilePath() {
  6042.       return this.profileInfo_.filePath;
  6043.     },
  6044.  
  6045.     /** @override */
  6046.     decorate: function() {
  6047.       DeletableItem.prototype.decorate.call(this);
  6048.  
  6049.       var profileInfo = this.profileInfo_;
  6050.  
  6051.       var iconEl = this.ownerDocument.createElement('img');
  6052.       iconEl.className = 'profile-img';
  6053.       iconEl.src = profileInfo.iconURL;
  6054.       this.contentElement.appendChild(iconEl);
  6055.  
  6056.       var nameEl = this.ownerDocument.createElement('div');
  6057.       if (profileInfo.isCurrentProfile)
  6058.         nameEl.classList.add('profile-item-current');
  6059.       this.contentElement.appendChild(nameEl);
  6060.  
  6061.       var displayName = profileInfo.name;
  6062.       if (profileInfo.isCurrentProfile) {
  6063.         displayName = loadTimeData.getStringF('profilesListItemCurrent',
  6064.                                               profileInfo.name);
  6065.       }
  6066.       nameEl.textContent = displayName;
  6067.  
  6068.       // Ensure that the button cannot be tabbed to for accessibility reasons.
  6069.       this.closeButtonElement.tabIndex = -1;
  6070.     },
  6071.   };
  6072.  
  6073.   var ProfileList = cr.ui.define('list');
  6074.  
  6075.   ProfileList.prototype = {
  6076.     __proto__: DeletableItemList.prototype,
  6077.  
  6078.     /** @override */
  6079.     decorate: function() {
  6080.       DeletableItemList.prototype.decorate.call(this);
  6081.       this.selectionModel = new ListSingleSelectionModel();
  6082.     },
  6083.  
  6084.     /** @override */
  6085.     createItem: function(pageInfo) {
  6086.       var item = new ProfileListItem(pageInfo);
  6087.       return item;
  6088.     },
  6089.  
  6090.     /** @override */
  6091.     deleteItemAtIndex: function(index) {
  6092.       ManageProfileOverlay.showDeleteDialog(this.dataModel.item(index));
  6093.     },
  6094.  
  6095.     /** @override */
  6096.     activateItemAtIndex: function(index) {
  6097.       // Don't allow the user to edit a profile that is not current.
  6098.       var profileInfo = this.dataModel.item(index);
  6099.       if (profileInfo.isCurrentProfile)
  6100.         ManageProfileOverlay.showManageDialog(profileInfo);
  6101.     },
  6102.   };
  6103.  
  6104.   return {
  6105.     ProfileList: ProfileList
  6106.   };
  6107. });
  6108.  
  6109.  
  6110. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6111. // Use of this source code is governed by a BSD-style license that can be
  6112. // found in the LICENSE file.
  6113.  
  6114. cr.define('options.browser_options', function() {
  6115.   /** @const */ var AutocompleteList = cr.ui.AutocompleteList;
  6116.   /** @const */ var InlineEditableItem = options.InlineEditableItem;
  6117.   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
  6118.  
  6119.   /**
  6120.    * Creates a new startup page list item.
  6121.    * @param {Object} pageInfo The page this item represents.
  6122.    * @constructor
  6123.    * @extends {cr.ui.ListItem}
  6124.    */
  6125.   function StartupPageListItem(pageInfo) {
  6126.     var el = cr.doc.createElement('div');
  6127.     el.pageInfo_ = pageInfo;
  6128.     StartupPageListItem.decorate(el);
  6129.     return el;
  6130.   }
  6131.  
  6132.   /**
  6133.    * Decorates an element as a startup page list item.
  6134.    * @param {!HTMLElement} el The element to decorate.
  6135.    */
  6136.   StartupPageListItem.decorate = function(el) {
  6137.     el.__proto__ = StartupPageListItem.prototype;
  6138.     el.decorate();
  6139.   };
  6140.  
  6141.   StartupPageListItem.prototype = {
  6142.     __proto__: InlineEditableItem.prototype,
  6143.  
  6144.     /**
  6145.      * Input field for editing the page url.
  6146.      * @type {HTMLElement}
  6147.      * @private
  6148.      */
  6149.     urlField_: null,
  6150.  
  6151.     /** @override */
  6152.     decorate: function() {
  6153.       InlineEditableItem.prototype.decorate.call(this);
  6154.  
  6155.       var pageInfo = this.pageInfo_;
  6156.  
  6157.       if (pageInfo.modelIndex == '-1') {
  6158.         this.isPlaceholder = true;
  6159.         pageInfo.title = loadTimeData.getString('startupAddLabel');
  6160.         pageInfo.url = '';
  6161.       }
  6162.  
  6163.       var titleEl = this.ownerDocument.createElement('div');
  6164.       titleEl.className = 'title';
  6165.       titleEl.classList.add('favicon-cell');
  6166.       titleEl.classList.add('weakrtl');
  6167.       titleEl.textContent = pageInfo.title;
  6168.       if (!this.isPlaceholder) {
  6169.         titleEl.style.backgroundImage = url(getFaviconURL(pageInfo.url));
  6170.         titleEl.title = pageInfo.tooltip;
  6171.       }
  6172.  
  6173.       this.contentElement.appendChild(titleEl);
  6174.  
  6175.       var urlEl = this.createEditableTextCell(pageInfo.url);
  6176.       urlEl.className = 'url';
  6177.       urlEl.classList.add('weakrtl');
  6178.       this.contentElement.appendChild(urlEl);
  6179.  
  6180.       var urlField = urlEl.querySelector('input');
  6181.       urlField.className = 'weakrtl';
  6182.       urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder');
  6183.       this.urlField_ = urlField;
  6184.  
  6185.       this.addEventListener('commitedit', this.onEditCommitted_);
  6186.  
  6187.       var self = this;
  6188.       urlField.addEventListener('focus', function(event) {
  6189.         self.parentNode.autocompleteList.attachToInput(urlField);
  6190.       });
  6191.       urlField.addEventListener('blur', function(event) {
  6192.         self.parentNode.autocompleteList.detach();
  6193.       });
  6194.  
  6195.       if (!this.isPlaceholder)
  6196.         this.draggable = true;
  6197.     },
  6198.  
  6199.     /** @override */
  6200.     get currentInputIsValid() {
  6201.       return this.urlField_.validity.valid;
  6202.     },
  6203.  
  6204.     /** @override */
  6205.     get hasBeenEdited() {
  6206.       return this.urlField_.value != this.pageInfo_.url;
  6207.     },
  6208.  
  6209.     /**
  6210.      * Called when committing an edit; updates the model.
  6211.      * @param {Event} e The end event.
  6212.      * @private
  6213.      */
  6214.     onEditCommitted_: function(e) {
  6215.       var url = this.urlField_.value;
  6216.       if (this.isPlaceholder)
  6217.         chrome.send('addStartupPage', [url]);
  6218.       else
  6219.         chrome.send('editStartupPage', [this.pageInfo_.modelIndex, url]);
  6220.     },
  6221.   };
  6222.  
  6223.   var StartupPageList = cr.ui.define('list');
  6224.  
  6225.   StartupPageList.prototype = {
  6226.     __proto__: InlineEditableItemList.prototype,
  6227.  
  6228.     /**
  6229.      * An autocomplete suggestion list for URL editing.
  6230.      * @type {AutocompleteList}
  6231.      */
  6232.     autocompleteList: null,
  6233.  
  6234.     /**
  6235.      * The drop position information: "below" or "above".
  6236.      */
  6237.     dropPos: null,
  6238.  
  6239.     /** @override */
  6240.     decorate: function() {
  6241.       InlineEditableItemList.prototype.decorate.call(this);
  6242.  
  6243.       // Listen to drag and drop events.
  6244.       this.addEventListener('dragstart', this.handleDragStart_.bind(this));
  6245.       this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
  6246.       this.addEventListener('dragover', this.handleDragOver_.bind(this));
  6247.       this.addEventListener('drop', this.handleDrop_.bind(this));
  6248.       this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
  6249.       this.addEventListener('dragend', this.handleDragEnd_.bind(this));
  6250.     },
  6251.  
  6252.     /** @override */
  6253.     createItem: function(pageInfo) {
  6254.       var item = new StartupPageListItem(pageInfo);
  6255.       item.urlField_.disabled = this.disabled;
  6256.       return item;
  6257.     },
  6258.  
  6259.     /** @override */
  6260.     deleteItemAtIndex: function(index) {
  6261.       chrome.send('removeStartupPages', [String(index)]);
  6262.     },
  6263.  
  6264.     /**
  6265.      * Computes the target item of drop event.
  6266.      * @param {Event} e The drop or dragover event.
  6267.      * @private
  6268.      */
  6269.     getTargetFromDropEvent_: function(e) {
  6270.       var target = e.target;
  6271.       // e.target may be an inner element of the list item
  6272.       while (target != null && !(target instanceof StartupPageListItem)) {
  6273.         target = target.parentNode;
  6274.       }
  6275.       return target;
  6276.     },
  6277.  
  6278.     /**
  6279.      * Handles the dragstart event.
  6280.      * @param {Event} e The dragstart event.
  6281.      * @private
  6282.      */
  6283.     handleDragStart_: function(e) {
  6284.       // Prevent dragging if the list is disabled.
  6285.       if (this.disabled) {
  6286.         e.preventDefault();
  6287.         return false;
  6288.       }
  6289.  
  6290.       var target = e.target;
  6291.       // StartupPageListItem should be the only draggable element type in the
  6292.       // page but let's make sure.
  6293.       if (target instanceof StartupPageListItem) {
  6294.         this.draggedItem = target;
  6295.         this.draggedItem.editable = false;
  6296.         e.dataTransfer.effectAllowed = 'move';
  6297.         // We need to put some kind of data in the drag or it will be
  6298.         // ignored.  Use the URL in case the user drags to a text field or the
  6299.         // desktop.
  6300.         e.dataTransfer.setData('text/plain', target.urlField_.value);
  6301.       }
  6302.     },
  6303.  
  6304.     /*
  6305.      * Handles the dragenter event.
  6306.      * @param {Event} e The dragenter event.
  6307.      * @private
  6308.      */
  6309.     handleDragEnter_: function(e) {
  6310.       e.preventDefault();
  6311.     },
  6312.  
  6313.     /*
  6314.      * Handles the dragover event.
  6315.      * @param {Event} e The dragover event.
  6316.      * @private
  6317.      */
  6318.     handleDragOver_: function(e) {
  6319.       var dropTarget = this.getTargetFromDropEvent_(e);
  6320.       // Determines whether the drop target is to accept the drop.
  6321.       // The drop is only successful on another StartupPageListItem.
  6322.       if (!(dropTarget instanceof StartupPageListItem) ||
  6323.           dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
  6324.         this.hideDropMarker_();
  6325.         return;
  6326.       }
  6327.       // Compute the drop postion. Should we move the dragged item to
  6328.       // below or above the drop target?
  6329.       var rect = dropTarget.getBoundingClientRect();
  6330.       var dy = e.clientY - rect.top;
  6331.       var yRatio = dy / rect.height;
  6332.       var dropPos = yRatio <= .5 ? 'above' : 'below';
  6333.       this.dropPos = dropPos;
  6334.       this.showDropMarker_(dropTarget, dropPos);
  6335.       e.preventDefault();
  6336.     },
  6337.  
  6338.     /*
  6339.      * Handles the drop event.
  6340.      * @param {Event} e The drop event.
  6341.      * @private
  6342.      */
  6343.     handleDrop_: function(e) {
  6344.       var dropTarget = this.getTargetFromDropEvent_(e);
  6345.       this.hideDropMarker_();
  6346.  
  6347.       // Insert the selection at the new position.
  6348.       var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
  6349.       if (this.dropPos == 'below')
  6350.         newIndex += 1;
  6351.  
  6352.       var selected = this.selectionModel.selectedIndexes;
  6353.       var stringized_selected = [];
  6354.       for (var j = 0; j < selected.length; j++)
  6355.         stringized_selected.push(String(selected[j]));
  6356.  
  6357.       chrome.send('dragDropStartupPage',
  6358.           [String(newIndex), stringized_selected]);
  6359.     },
  6360.  
  6361.     /**
  6362.      * Handles the dragleave event.
  6363.      * @param {Event} e The dragleave event.
  6364.      * @private
  6365.      */
  6366.     handleDragLeave_: function(e) {
  6367.       this.hideDropMarker_();
  6368.     },
  6369.  
  6370.     /**
  6371.      * Handles the dragend event.
  6372.      * @param {Event} e The dragend event.
  6373.      * @private
  6374.      */
  6375.     handleDragEnd_: function(e) {
  6376.       this.draggedItem.editable = true;
  6377.       this.draggedItem.updateEditState();
  6378.     },
  6379.  
  6380.     /**
  6381.      * Shows and positions the marker to indicate the drop target.
  6382.      * @param {HTMLElement} target The current target list item of drop.
  6383.      * @param {string} pos 'below' or 'above'.
  6384.      * @private
  6385.      */
  6386.     showDropMarker_: function(target, pos) {
  6387.       window.clearTimeout(this.hideDropMarkerTimer_);
  6388.       var marker = $('startupPagesListDropmarker');
  6389.       var rect = target.getBoundingClientRect();
  6390.       var markerHeight = 6;
  6391.       if (pos == 'above') {
  6392.         marker.style.top = (rect.top - markerHeight / 2) + 'px';
  6393.       } else {
  6394.         marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
  6395.       }
  6396.       marker.style.width = rect.width + 'px';
  6397.       marker.style.left = rect.left + 'px';
  6398.       marker.style.display = 'block';
  6399.     },
  6400.  
  6401.     /**
  6402.      * Hides the drop marker.
  6403.      * @private
  6404.      */
  6405.     hideDropMarker_: function() {
  6406.       // Hide the marker in a timeout to reduce flickering as we move between
  6407.       // valid drop targets.
  6408.       window.clearTimeout(this.hideDropMarkerTimer_);
  6409.       this.hideDropMarkerTimer_ = window.setTimeout(function() {
  6410.         $('startupPagesListDropmarker').style.display = '';
  6411.       }, 100);
  6412.     },
  6413.   };
  6414.  
  6415.   return {
  6416.     StartupPageList: StartupPageList
  6417.   };
  6418. });
  6419.  
  6420. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6421. // Use of this source code is governed by a BSD-style license that can be
  6422. // found in the LICENSE file.
  6423.  
  6424. cr.define('options', function() {
  6425.   var OptionsPage = options.OptionsPage;
  6426.  
  6427.   /**
  6428.    * ClearBrowserDataOverlay class
  6429.    * Encapsulated handling of the 'Clear Browser Data' overlay page.
  6430.    * @class
  6431.    */
  6432.   function ClearBrowserDataOverlay() {
  6433.     OptionsPage.call(this, 'clearBrowserData',
  6434.                      loadTimeData.getString('clearBrowserDataOverlayTabTitle'),
  6435.                      'clear-browser-data-overlay');
  6436.   }
  6437.  
  6438.   cr.addSingletonGetter(ClearBrowserDataOverlay);
  6439.  
  6440.   ClearBrowserDataOverlay.prototype = {
  6441.     // Inherit ClearBrowserDataOverlay from OptionsPage.
  6442.     __proto__: OptionsPage.prototype,
  6443.  
  6444.     /**
  6445.      * Initialize the page.
  6446.      */
  6447.     initializePage: function() {
  6448.       // Call base class implementation to starts preference initialization.
  6449.       OptionsPage.prototype.initializePage.call(this);
  6450.  
  6451.       var f = this.updateCommitButtonState_.bind(this);
  6452.       var types = ['browser.clear_data.browsing_history',
  6453.                    'browser.clear_data.download_history',
  6454.                    'browser.clear_data.cache',
  6455.                    'browser.clear_data.cookies',
  6456.                    'browser.clear_data.passwords',
  6457.                    'browser.clear_data.form_data',
  6458.                    'browser.clear_data.hosted_apps_data',
  6459.                    'browser.clear_data.content_licenses'];
  6460.       types.forEach(function(type) {
  6461.           Preferences.getInstance().addEventListener(type, f);
  6462.       });
  6463.  
  6464.       var checkboxes = document.querySelectorAll(
  6465.           '#cbd-content-area input[type=checkbox]');
  6466.       for (var i = 0; i < checkboxes.length; i++) {
  6467.         checkboxes[i].onclick = f;
  6468.       }
  6469.       this.updateCommitButtonState_();
  6470.  
  6471.       $('clear-browser-data-dismiss').onclick = function(event) {
  6472.         ClearBrowserDataOverlay.dismiss();
  6473.       };
  6474.       $('clear-browser-data-commit').onclick = function(event) {
  6475.         ClearBrowserDataOverlay.setClearingState(true);
  6476.         chrome.send('performClearBrowserData');
  6477.       };
  6478.     },
  6479.  
  6480.     // Set the enabled state of the commit button.
  6481.     updateCommitButtonState_: function() {
  6482.       var checkboxes = document.querySelectorAll(
  6483.           '#cbd-content-area input[type=checkbox]');
  6484.       var isChecked = false;
  6485.       for (var i = 0; i < checkboxes.length; i++) {
  6486.         if (checkboxes[i].checked) {
  6487.           isChecked = true;
  6488.           break;
  6489.         }
  6490.       }
  6491.       $('clear-browser-data-commit').disabled = !isChecked;
  6492.     },
  6493.   };
  6494.  
  6495.   //
  6496.   // Chrome callbacks
  6497.   //
  6498.   ClearBrowserDataOverlay.setClearingState = function(state) {
  6499.     $('delete-browsing-history-checkbox').disabled = state;
  6500.     $('delete-download-history-checkbox').disabled = state;
  6501.     $('delete-cache-checkbox').disabled = state;
  6502.     $('delete-cookies-checkbox').disabled = state;
  6503.     $('delete-passwords-checkbox').disabled = state;
  6504.     $('delete-form-data-checkbox').disabled = state;
  6505.     $('delete-hosted-apps-data-checkbox').disabled = state;
  6506.     $('deauthorize-content-licenses-checkbox').disabled = state;
  6507.     $('clear-browser-data-time-period').disabled = state;
  6508.     $('cbd-throbber').style.visibility = state ? 'visible' : 'hidden';
  6509.     $('clear-browser-data-dismiss').disabled = state;
  6510.  
  6511.     if (state)
  6512.       $('clear-browser-data-commit').disabled = true;
  6513.     else
  6514.       ClearBrowserDataOverlay.getInstance().updateCommitButtonState_();
  6515.   };
  6516.  
  6517.   ClearBrowserDataOverlay.doneClearing = function() {
  6518.     // The delay gives the user some feedback that the clearing
  6519.     // actually worked. Otherwise the dialog just vanishes instantly in most
  6520.     // cases.
  6521.     window.setTimeout(function() {
  6522.       ClearBrowserDataOverlay.dismiss();
  6523.     }, 200);
  6524.   };
  6525.  
  6526.   ClearBrowserDataOverlay.dismiss = function() {
  6527.     OptionsPage.closeOverlay();
  6528.     this.setClearingState(false);
  6529.   };
  6530.  
  6531.   // Export
  6532.   return {
  6533.     ClearBrowserDataOverlay: ClearBrowserDataOverlay
  6534.   };
  6535. });
  6536.  
  6537. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6538. // Use of this source code is governed by a BSD-style license that can be
  6539. // found in the LICENSE file.
  6540.  
  6541. cr.define('options', function() {
  6542.   /** @const */ var OptionsPage = options.OptionsPage;
  6543.  
  6544.   /**
  6545.    * A dialog that will pop up when the user attempts to set the value of the
  6546.    * Boolean |pref| to |true|, asking for confirmation. If the user clicks OK,
  6547.    * the new value is committed to Chrome. If the user clicks Cancel or leaves
  6548.    * the settings page, the new value is discarded.
  6549.    * @constructor
  6550.    * @param {string} name See OptionsPage constructor.
  6551.    * @param {string} title See OptionsPage constructor.
  6552.    * @param {string} pageDivName See OptionsPage constructor.
  6553.    * @param {HTMLInputElement} okButton The confirmation button element.
  6554.    * @param {HTMLInputElement} cancelButton The cancellation button element.
  6555.    * @param {string} pref The pref that requires confirmation.
  6556.    * @param {string} metric User metrics identifier.
  6557.    * @param {string} confirmed_pref A pref used to remember whether the user has
  6558.    *     confirmed the dialog before. This ensures that the user is presented
  6559.    *     with the dialog only once. If left |undefined| or |null|, the dialog
  6560.    *     will pop up every time the user attempts to set |pref| to |true|.
  6561.    * @extends {OptionsPage}
  6562.    */
  6563.   function ConfirmDialog(name, title, pageDivName, okButton, cancelButton, pref,
  6564.                          metric, confirmed_pref) {
  6565.     OptionsPage.call(this, name, title, pageDivName);
  6566.     this.okButton = okButton;
  6567.     this.cancelButton = cancelButton;
  6568.     this.pref = pref;
  6569.     this.metric = metric;
  6570.     this.confirmed_pref = confirmed_pref;
  6571.     this.confirmed_ = false;
  6572.   }
  6573.  
  6574.   ConfirmDialog.prototype = {
  6575.     // Set up the prototype chain
  6576.     __proto__: OptionsPage.prototype,
  6577.  
  6578.     /**
  6579.      * Handle changes to |pref|. Only uncommitted changes are relevant as these
  6580.      * originate from user and need to be explicitly committed to take effect.
  6581.      * Pop up the dialog or commit the change, depending on whether confirmation
  6582.      * is needed.
  6583.      * @param {Event} event Change event.
  6584.      * @private
  6585.      */
  6586.     onPrefChanged_: function(event) {
  6587.       if (!event.value.uncommitted)
  6588.         return;
  6589.  
  6590.       if (event.value.value && !this.confirmed_)
  6591.         OptionsPage.showPageByName(this.name, false);
  6592.       else
  6593.         Preferences.getInstance().commitPref(this.pref, this.metric);
  6594.     },
  6595.  
  6596.     /**
  6597.      * Handle changes to |confirmed_pref| by caching them.
  6598.      * @param {Event} event Change event.
  6599.      * @private
  6600.      */
  6601.     onConfirmedChanged_: function(event) {
  6602.       this.confirmed_ = event.value.value;
  6603.     },
  6604.  
  6605.     /** @override */
  6606.     initializePage: function() {
  6607.       this.okButton.onclick = this.handleConfirm.bind(this);
  6608.       this.cancelButton.onclick = this.handleCancel.bind(this);
  6609.       Preferences.getInstance().addEventListener(
  6610.           this.pref, this.onPrefChanged_.bind(this));
  6611.       if (this.confirmed_pref) {
  6612.         Preferences.getInstance().addEventListener(
  6613.             this.confirmed_pref, this.onConfirmedChanged_.bind(this));
  6614.       }
  6615.     },
  6616.  
  6617.     /**
  6618.      * Handle the confirm button by committing the |pref| change. If
  6619.      * |confirmed_pref| has been specified, also remember that the dialog has
  6620.      * been confirmed to avoid bringing it up in the future.
  6621.      */
  6622.     handleConfirm: function() {
  6623.       OptionsPage.closeOverlay();
  6624.  
  6625.       Preferences.getInstance().commitPref(this.pref, this.metric);
  6626.       if (this.confirmed_pref)
  6627.         Preferences.setBooleanPref(this.confirmed_pref, true, true);
  6628.     },
  6629.  
  6630.     /**
  6631.      * Handle the cancel button by rolling back the |pref| change without it
  6632.      * ever taking effect.
  6633.      */
  6634.     handleCancel: function() {
  6635.       OptionsPage.closeOverlay();
  6636.  
  6637.       Preferences.getInstance().rollbackPref(this.pref);
  6638.     },
  6639.   };
  6640.  
  6641.   return {
  6642.     ConfirmDialog: ConfirmDialog
  6643.   };
  6644. });
  6645.  
  6646. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6647. // Use of this source code is governed by a BSD-style license that can be
  6648. // found in the LICENSE file.
  6649.  
  6650. if (!loadTimeData.getBoolean('newContentSettings')) {
  6651.  
  6652. cr.define('options', function() {
  6653.   /** @const */ var OptionsPage = options.OptionsPage;
  6654.  
  6655.   //////////////////////////////////////////////////////////////////////////////
  6656.   // ContentSettings class:
  6657.  
  6658.   /**
  6659.    * Encapsulated handling of content settings page.
  6660.    * @constructor
  6661.    */
  6662.   function ContentSettings() {
  6663.     this.activeNavTab = null;
  6664.     OptionsPage.call(this, 'content',
  6665.                      loadTimeData.getString('contentSettingsPageTabTitle'),
  6666.                      'content-settings-page');
  6667.   }
  6668.  
  6669.   cr.addSingletonGetter(ContentSettings);
  6670.  
  6671.   ContentSettings.prototype = {
  6672.     __proto__: OptionsPage.prototype,
  6673.  
  6674.     initializePage: function() {
  6675.       OptionsPage.prototype.initializePage.call(this);
  6676.  
  6677.       chrome.send('getContentFilterSettings');
  6678.  
  6679.       var exceptionsButtons =
  6680.           this.pageDiv.querySelectorAll('.exceptions-list-button');
  6681.       for (var i = 0; i < exceptionsButtons.length; i++) {
  6682.         exceptionsButtons[i].onclick = function(event) {
  6683.           var page = ContentSettingsExceptionsArea.getInstance();
  6684.  
  6685.           // Add on the proper hash for the content type, and store that in the
  6686.           // history so back/forward and tab restore works.
  6687.           var hash = event.currentTarget.getAttribute('contentType');
  6688.           var url = page.name + '#' + hash;
  6689.           window.history.replaceState({pageName: page.name},
  6690.                                       page.title,
  6691.                                       '/' + url);
  6692.  
  6693.           // Navigate after the history has been replaced in order to have the
  6694.           // correct hash loaded.
  6695.           OptionsPage.navigateToPage('contentExceptions');
  6696.  
  6697.           uber.invokeMethodOnParent('setPath', {path: url});
  6698.           uber.invokeMethodOnParent('setTitle',
  6699.               {title: loadTimeData.getString(hash + 'TabTitle')});
  6700.         };
  6701.       }
  6702.  
  6703.       var manageHandlersButton = $('manage-handlers-button');
  6704.       if (manageHandlersButton) {
  6705.         manageHandlersButton.onclick = function(event) {
  6706.           OptionsPage.navigateToPage('handlers');
  6707.         };
  6708.       }
  6709.  
  6710.       $('manage-galleries-button').onclick = function(event) {
  6711.         OptionsPage.navigateToPage('manageGalleries');
  6712.       };
  6713.  
  6714.       if (cr.isChromeOS)
  6715.         UIAccountTweaks.applyGuestModeVisibility(document);
  6716.  
  6717.       // Cookies filter page ---------------------------------------------------
  6718.       $('show-cookies-button').onclick = function(event) {
  6719.         chrome.send('coreOptionsUserMetricsAction', ['Options_ShowCookies']);
  6720.         OptionsPage.navigateToPage('cookies');
  6721.       };
  6722.  
  6723.       $('content-settings-overlay-confirm').onclick =
  6724.           OptionsPage.closeOverlay.bind(OptionsPage);
  6725.  
  6726.       $('media-pepper-flash-default').hidden = true;
  6727.       $('media-pepper-flash-exceptions').hidden = true;
  6728.  
  6729.       $('media-select-mic').addEventListener('change',
  6730.           ContentSettings.setDefaultMicrophone_);
  6731.       $('media-select-camera').addEventListener('change',
  6732.           ContentSettings.setDefaultCamera_);
  6733.     },
  6734.   };
  6735.  
  6736.   ContentSettings.updateHandlersEnabledRadios = function(enabled) {
  6737.     var selector = '#content-settings-page input[type=radio][value=' +
  6738.         (enabled ? 'allow' : 'block') + '].handler-radio';
  6739.     document.querySelector(selector).checked = true;
  6740.   };
  6741.  
  6742.   /**
  6743.    * Sets the values for all the content settings radios.
  6744.    * @param {Object} dict A mapping from radio groups to the checked value for
  6745.    *     that group.
  6746.    */
  6747.   ContentSettings.setContentFilterSettingsValue = function(dict) {
  6748.     for (var group in dict) {
  6749.       var managedBy = dict[group].managedBy;
  6750.       var controlledBy = managedBy == 'policy' || managedBy == 'extension' ?
  6751.           managedBy : null;
  6752.       document.querySelector('input[type=radio][name=' + group + '][value=' +
  6753.                              dict[group].value + ']').checked = true;
  6754.       var radios = document.querySelectorAll('input[type=radio][name=' +
  6755.                                              group + ']');
  6756.       for (var i = 0, len = radios.length; i < len; i++) {
  6757.         radios[i].disabled = (managedBy != 'default');
  6758.         radios[i].controlledBy = controlledBy;
  6759.       }
  6760.       var indicators = document.querySelectorAll(
  6761.           'span.controlled-setting-indicator[content-setting=' + group + ']');
  6762.       if (indicators.length == 0)
  6763.         continue;
  6764.       // Create a synthetic pref change event decorated as
  6765.       // CoreOptionsHandler::CreateValueForPref() does.
  6766.       var event = new cr.Event(group);
  6767.       event.value = {
  6768.         value: dict[group].value,
  6769.         controlledBy: controlledBy,
  6770.       };
  6771.       for (var i = 0; i < indicators.length; i++)
  6772.         indicators[i].handlePrefChange(event);
  6773.     }
  6774.   };
  6775.  
  6776.   /**
  6777.    * Updates the labels and indicators for the Media settings. Those require
  6778.    * special handling because they are backed by multiple prefs and can change
  6779.    * their scope based on the managed state of the backing prefs.
  6780.    * @param {Object} mediaSettings A dictionary containing the following fields:
  6781.    *     {String} askText The label for the ask radio button.
  6782.    *     {String} blockText The label for the block radio button.
  6783.    *     {Boolean} cameraDisabled Whether to disable the camera dropdown.
  6784.    *     {Boolean} micDisabled Whether to disable the microphone dropdown.
  6785.    *     {Boolean} showBubble Wether to show the managed icon and bubble for the
  6786.    *         media label.
  6787.    *     {String} bubbleText The text to use inside the bubble if it is shown.
  6788.    */
  6789.   ContentSettings.updateMediaUI = function(mediaSettings) {
  6790.     $('media-stream-ask-label').innerHTML =
  6791.         loadTimeData.getString(mediaSettings.askText);
  6792.     $('media-stream-block-label').innerHTML =
  6793.         loadTimeData.getString(mediaSettings.blockText);
  6794.  
  6795.     if (mediaSettings.micDisabled)
  6796.       $('media-select-mic').disabled = true;
  6797.     if (mediaSettings.cameraDisabled)
  6798.       $('media-select-camera').disabled = true;
  6799.  
  6800.     OptionsPage.hideBubble();
  6801.     // Create a synthetic pref change event decorated as
  6802.     // CoreOptionsHandler::CreateValueForPref() does.
  6803.     var event = new cr.Event();
  6804.     event.value = {};
  6805.  
  6806.     if (mediaSettings.showBubble) {
  6807.       event.value = { controlledBy: 'policy' };
  6808.       $('media-indicator').setAttribute(
  6809.           'textpolicy', loadTimeData.getString(mediaSettings.bubbleText));
  6810.       $('media-indicator').location = cr.ui.ArrowLocation.TOP_START;
  6811.     }
  6812.  
  6813.     $('media-indicator').handlePrefChange(event);
  6814.   };
  6815.  
  6816.   /**
  6817.    * Initializes an exceptions list.
  6818.    * @param {string} type The content type that we are setting exceptions for.
  6819.    * @param {Array} list An array of pairs, where the first element of each pair
  6820.    *     is the filter string, and the second is the setting (allow/block).
  6821.    */
  6822.   ContentSettings.setExceptions = function(type, list) {
  6823.     var exceptionsList =
  6824.         document.querySelector('div[contentType=' + type + ']' +
  6825.                                ' list[mode=normal]');
  6826.     exceptionsList.setExceptions(list);
  6827.   };
  6828.  
  6829.   ContentSettings.setHandlers = function(list) {
  6830.     $('handlers-list').setHandlers(list);
  6831.   };
  6832.  
  6833.   ContentSettings.setIgnoredHandlers = function(list) {
  6834.     $('ignored-handlers-list').setHandlers(list);
  6835.   };
  6836.  
  6837.   ContentSettings.setOTRExceptions = function(type, list) {
  6838.     var exceptionsList =
  6839.         document.querySelector('div[contentType=' + type + ']' +
  6840.                                ' list[mode=otr]');
  6841.  
  6842.     exceptionsList.parentNode.hidden = false;
  6843.     exceptionsList.setExceptions(list);
  6844.   };
  6845.  
  6846.   /**
  6847.    * The browser's response to a request to check the validity of a given URL
  6848.    * pattern.
  6849.    * @param {string} type The content type.
  6850.    * @param {string} mode The browser mode.
  6851.    * @param {string} pattern The pattern.
  6852.    * @param {bool} valid Whether said pattern is valid in the context of
  6853.    *     a content exception setting.
  6854.    */
  6855.   ContentSettings.patternValidityCheckComplete =
  6856.       function(type, mode, pattern, valid) {
  6857.     var exceptionsList =
  6858.         document.querySelector('div[contentType=' + type + '] ' +
  6859.                                'list[mode=' + mode + ']');
  6860.     exceptionsList.patternValidityCheckComplete(pattern, valid);
  6861.   };
  6862.  
  6863.   /**
  6864.    * Shows/hides the link to the Pepper Flash camera and microphone default
  6865.    * settings.
  6866.    * Please note that whether the link is actually showed or not is also
  6867.    * affected by the style class pepper-flash-settings.
  6868.    */
  6869.   ContentSettings.showMediaPepperFlashDefaultLink = function(show) {
  6870.     $('media-pepper-flash-default').hidden = !show;
  6871.   }
  6872.  
  6873.   /**
  6874.    * Shows/hides the link to the Pepper Flash camera and microphone
  6875.    * site-specific settings.
  6876.    * Please note that whether the link is actually showed or not is also
  6877.    * affected by the style class pepper-flash-settings.
  6878.    */
  6879.   ContentSettings.showMediaPepperFlashExceptionsLink = function(show) {
  6880.     $('media-pepper-flash-exceptions').hidden = !show;
  6881.   }
  6882.  
  6883.   /**
  6884.    * Updates the microphone/camera devices menu with the given entries.
  6885.    * @param {string} type The device type.
  6886.    * @param {Array} devices List of available devices.
  6887.    * @param {string} defaultdevice The unique id of the current default device.
  6888.    */
  6889.   ContentSettings.updateDevicesMenu = function(type, devices, defaultdevice) {
  6890.     var deviceSelect = '';
  6891.     if (type == 'mic') {
  6892.       deviceSelect = $('media-select-mic');
  6893.     } else if (type == 'camera') {
  6894.       deviceSelect = $('media-select-camera');
  6895.     } else {
  6896.       console.error('Unknown device type for <device select> UI element: ' +
  6897.                     type);
  6898.       return;
  6899.     }
  6900.  
  6901.     deviceSelect.textContent = '';
  6902.  
  6903.     var deviceCount = devices.length;
  6904.     var defaultIndex = -1;
  6905.     for (var i = 0; i < deviceCount; i++) {
  6906.       var device = devices[i];
  6907.       var option = new Option(device.name, device.id);
  6908.       if (option.value == defaultdevice)
  6909.         defaultIndex = i;
  6910.       deviceSelect.appendChild(option);
  6911.     }
  6912.     if (defaultIndex >= 0)
  6913.       deviceSelect.selectedIndex = defaultIndex;
  6914.   };
  6915.  
  6916.   /**
  6917.    * Set the default microphone device based on the popup selection.
  6918.    * @private
  6919.    */
  6920.   ContentSettings.setDefaultMicrophone_ = function() {
  6921.     var deviceSelect = $('media-select-mic');
  6922.     chrome.send('setDefaultCaptureDevice', ['mic', deviceSelect.value]);
  6923.   };
  6924.  
  6925.   /**
  6926.    * Set the default camera device based on the popup selection.
  6927.    * @private
  6928.    */
  6929.   ContentSettings.setDefaultCamera_ = function() {
  6930.     var deviceSelect = $('media-select-camera');
  6931.     chrome.send('setDefaultCaptureDevice', ['camera', deviceSelect.value]);
  6932.   };
  6933.  
  6934.   // Export
  6935.   return {
  6936.     ContentSettings: ContentSettings
  6937.   };
  6938.  
  6939. });
  6940.  
  6941. }
  6942.  
  6943. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  6944. // Use of this source code is governed by a BSD-style license that can be
  6945. // found in the LICENSE file.
  6946.  
  6947. if (loadTimeData.getBoolean('newContentSettings')) {
  6948.  
  6949. cr.define('options', function() {
  6950.   /** @const */ var OptionsPage = options.OptionsPage;
  6951.  
  6952.   //////////////////////////////////////////////////////////////////////////////
  6953.   // ContentSettings class:
  6954.  
  6955.   /**
  6956.    * Encapsulated handling of content settings page.
  6957.    * @constructor
  6958.    */
  6959.   function ContentSettings() {
  6960.     this.activeNavTab = null;
  6961.     OptionsPage.call(this, 'content',
  6962.                      loadTimeData.getString('contentSettingsPageTabTitle'),
  6963.                      'content-settings-page2');
  6964.   }
  6965.  
  6966.   cr.addSingletonGetter(ContentSettings);
  6967.  
  6968.   ContentSettings.prototype = {
  6969.     __proto__: OptionsPage.prototype,
  6970.  
  6971.     initializePage: function() {
  6972.       OptionsPage.prototype.initializePage.call(this);
  6973.  
  6974.       $('content-settings-overlay-confirm2').onclick =
  6975.           OptionsPage.closeOverlay.bind(OptionsPage);
  6976.     },
  6977.   };
  6978.  
  6979.   ContentSettings.updateHandlersEnabledRadios = function(enabled) {
  6980.     // Not implemented.
  6981.   };
  6982.  
  6983.   /**
  6984.    * Sets the values for all the content settings radios.
  6985.    * @param {Object} dict A mapping from radio groups to the checked value for
  6986.    *     that group.
  6987.    */
  6988.   ContentSettings.setContentFilterSettingsValue = function(dict) {
  6989.     // Not implemented.
  6990.   };
  6991.  
  6992.   /**
  6993.    * Initializes an exceptions list.
  6994.    * @param {string} type The content type that we are setting exceptions for.
  6995.    * @param {Array} list An array of pairs, where the first element of each pair
  6996.    *     is the filter string, and the second is the setting (allow/block).
  6997.    */
  6998.   ContentSettings.setExceptions = function(type, list) {
  6999.     // Not implemented.
  7000.   };
  7001.  
  7002.   ContentSettings.setHandlers = function(list) {
  7003.     // Not implemented.
  7004.   };
  7005.  
  7006.   ContentSettings.setIgnoredHandlers = function(list) {
  7007.     // Not implemented.
  7008.   };
  7009.  
  7010.   ContentSettings.setOTRExceptions = function(type, list) {
  7011.     // Not implemented.
  7012.   };
  7013.  
  7014.   /**
  7015.    * Enables the Pepper Flash camera and microphone settings.
  7016.    * Please note that whether the settings are actually showed or not is also
  7017.    * affected by the style class pepper-flash-settings.
  7018.    */
  7019.   ContentSettings.enablePepperFlashCameraMicSettings = function() {
  7020.     // Not implemented.
  7021.   }
  7022.  
  7023.   // Export
  7024.   return {
  7025.     ContentSettings: ContentSettings
  7026.   };
  7027.  
  7028. });
  7029.  
  7030. }
  7031.  
  7032. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7033. // Use of this source code is governed by a BSD-style license that can be
  7034. // found in the LICENSE file.
  7035.  
  7036. cr.define('options.contentSettings', function() {
  7037.   /** @const */ var ControlledSettingIndicator =
  7038.                     options.ControlledSettingIndicator;
  7039.   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
  7040.   /** @const */ var InlineEditableItem = options.InlineEditableItem;
  7041.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  7042.  
  7043.   /**
  7044.    * Creates a new exceptions list item.
  7045.    *
  7046.    * @param {string} contentType The type of the list.
  7047.    * @param {string} mode The browser mode, 'otr' or 'normal'.
  7048.    * @param {boolean} enableAskOption Whether to show an 'ask every time'
  7049.    *     option in the select.
  7050.    * @param {Object} exception A dictionary that contains the data of the
  7051.    *     exception.
  7052.    * @constructor
  7053.    * @extends {options.InlineEditableItem}
  7054.    */
  7055.   function ExceptionsListItem(contentType, mode, enableAskOption, exception) {
  7056.     var el = cr.doc.createElement('div');
  7057.     el.mode = mode;
  7058.     el.contentType = contentType;
  7059.     el.enableAskOption = enableAskOption;
  7060.     el.dataItem = exception;
  7061.     el.__proto__ = ExceptionsListItem.prototype;
  7062.     el.decorate();
  7063.  
  7064.     return el;
  7065.   }
  7066.  
  7067.   ExceptionsListItem.prototype = {
  7068.     __proto__: InlineEditableItem.prototype,
  7069.  
  7070.     /**
  7071.      * Called when an element is decorated as a list item.
  7072.      */
  7073.     decorate: function() {
  7074.       InlineEditableItem.prototype.decorate.call(this);
  7075.  
  7076.       this.isPlaceholder = !this.pattern;
  7077.       var patternCell = this.createEditableTextCell(this.pattern);
  7078.       patternCell.className = 'exception-pattern';
  7079.       patternCell.classList.add('weakrtl');
  7080.       this.contentElement.appendChild(patternCell);
  7081.       if (this.pattern)
  7082.         this.patternLabel = patternCell.querySelector('.static-text');
  7083.       var input = patternCell.querySelector('input');
  7084.  
  7085.       // TODO(stuartmorgan): Create an createEditableSelectCell abstracting
  7086.       // this code.
  7087.       // Setting label for display mode. |pattern| will be null for the 'add new
  7088.       // exception' row.
  7089.       if (this.pattern) {
  7090.         var settingLabel = cr.doc.createElement('span');
  7091.         settingLabel.textContent = this.settingForDisplay();
  7092.         settingLabel.className = 'exception-setting';
  7093.         settingLabel.setAttribute('displaymode', 'static');
  7094.         this.contentElement.appendChild(settingLabel);
  7095.         this.settingLabel = settingLabel;
  7096.       }
  7097.  
  7098.       // Setting select element for edit mode.
  7099.       var select = cr.doc.createElement('select');
  7100.       var optionAllow = cr.doc.createElement('option');
  7101.       optionAllow.textContent = loadTimeData.getString('allowException');
  7102.       optionAllow.value = 'allow';
  7103.       select.appendChild(optionAllow);
  7104.  
  7105.       if (this.enableAskOption) {
  7106.         var optionAsk = cr.doc.createElement('option');
  7107.         optionAsk.textContent = loadTimeData.getString('askException');
  7108.         optionAsk.value = 'ask';
  7109.         select.appendChild(optionAsk);
  7110.       }
  7111.  
  7112.       if (this.contentType == 'cookies') {
  7113.         var optionSession = cr.doc.createElement('option');
  7114.         optionSession.textContent = loadTimeData.getString('sessionException');
  7115.         optionSession.value = 'session';
  7116.         select.appendChild(optionSession);
  7117.       }
  7118.  
  7119.       if (this.contentType != 'fullscreen') {
  7120.         var optionBlock = cr.doc.createElement('option');
  7121.         optionBlock.textContent = loadTimeData.getString('blockException');
  7122.         optionBlock.value = 'block';
  7123.         select.appendChild(optionBlock);
  7124.       }
  7125.  
  7126.       if (this.isEmbeddingRule()) {
  7127.         this.patternLabel.classList.add('sublabel');
  7128.         this.editable = false;
  7129.       }
  7130.  
  7131.       if (this.setting == 'default') {
  7132.         // Items that don't have their own settings (parents of 'embedded on'
  7133.         // items) aren't deletable.
  7134.         this.deletable = false;
  7135.         this.editable = false;
  7136.       }
  7137.  
  7138.       this.contentElement.appendChild(select);
  7139.       select.className = 'exception-setting';
  7140.       if (this.pattern)
  7141.         select.setAttribute('displaymode', 'edit');
  7142.  
  7143.       if (this.contentType == 'media-stream') {
  7144.         this.settingLabel.classList.add('media-audio-setting');
  7145.  
  7146.         var videoSettingLabel = cr.doc.createElement('span');
  7147.         videoSettingLabel.textContent = this.videoSettingForDisplay();
  7148.         videoSettingLabel.className = 'exception-setting';
  7149.         videoSettingLabel.classList.add('media-video-setting');
  7150.         videoSettingLabel.setAttribute('displaymode', 'static');
  7151.         this.contentElement.appendChild(videoSettingLabel);
  7152.       }
  7153.  
  7154.       // Used to track whether the URL pattern in the input is valid.
  7155.       // This will be true if the browser process has informed us that the
  7156.       // current text in the input is valid. Changing the text resets this to
  7157.       // false, and getting a response from the browser sets it back to true.
  7158.       // It starts off as false for empty string (new exceptions) or true for
  7159.       // already-existing exceptions (which we assume are valid).
  7160.       this.inputValidityKnown = this.pattern;
  7161.       // This one tracks the actual validity of the pattern in the input. This
  7162.       // starts off as true so as not to annoy the user when he adds a new and
  7163.       // empty input.
  7164.       this.inputIsValid = true;
  7165.  
  7166.       this.input = input;
  7167.       this.select = select;
  7168.  
  7169.       this.updateEditables();
  7170.  
  7171.       // Editing notifications, geolocation and media-stream is disabled for
  7172.       // now.
  7173.       if (this.contentType == 'notifications' ||
  7174.           this.contentType == 'location' ||
  7175.           this.contentType == 'media-stream') {
  7176.         this.editable = false;
  7177.       }
  7178.  
  7179.       // If the source of the content setting exception is not a user
  7180.       // preference, that source controls the exception and the user cannot edit
  7181.       // or delete it.
  7182.       var controlledBy =
  7183.           this.dataItem.source && this.dataItem.source != 'preference' ?
  7184.               this.dataItem.source : null;
  7185.  
  7186.       if (controlledBy) {
  7187.         this.setAttribute('controlled-by', controlledBy);
  7188.         this.deletable = false;
  7189.         this.editable = false;
  7190.       }
  7191.  
  7192.       if (controlledBy == 'policy' || controlledBy == 'extension') {
  7193.         this.querySelector('.row-delete-button').hidden = true;
  7194.         var indicator = ControlledSettingIndicator();
  7195.         indicator.setAttribute('content-exception', this.contentType);
  7196.         // Create a synthetic pref change event decorated as
  7197.         // CoreOptionsHandler::CreateValueForPref() does.
  7198.         var event = new cr.Event(this.contentType);
  7199.         event.value = { controlledBy: controlledBy };
  7200.         indicator.handlePrefChange(event);
  7201.         this.appendChild(indicator);
  7202.       }
  7203.  
  7204.       // If the exception comes from a hosted app, display the name and the
  7205.       // icon of the app.
  7206.       if (controlledBy == 'HostedApp') {
  7207.         this.title =
  7208.             loadTimeData.getString('set_by') + ' ' + this.dataItem.appName;
  7209.         var button = this.querySelector('.row-delete-button');
  7210.         // Use the host app's favicon (16px, match bigger size).
  7211.         // See c/b/ui/webui/extensions/extension_icon_source.h
  7212.         // for a description of the chrome://extension-icon URL.
  7213.         button.style.backgroundImage =
  7214.             'url(\'chrome://extension-icon/' + this.dataItem.appId + '/16/1\')';
  7215.       }
  7216.  
  7217.       var listItem = this;
  7218.       // Handle events on the editable nodes.
  7219.       input.oninput = function(event) {
  7220.         listItem.inputValidityKnown = false;
  7221.         chrome.send('checkExceptionPatternValidity',
  7222.                     [listItem.contentType, listItem.mode, input.value]);
  7223.       };
  7224.  
  7225.       // Listen for edit events.
  7226.       this.addEventListener('canceledit', this.onEditCancelled_);
  7227.       this.addEventListener('commitedit', this.onEditCommitted_);
  7228.     },
  7229.  
  7230.     isEmbeddingRule: function() {
  7231.       return this.dataItem.embeddingOrigin &&
  7232.           this.dataItem.embeddingOrigin !== this.dataItem.origin;
  7233.     },
  7234.  
  7235.     /**
  7236.      * The pattern (e.g., a URL) for the exception.
  7237.      *
  7238.      * @type {string}
  7239.      */
  7240.     get pattern() {
  7241.       if (!this.isEmbeddingRule()) {
  7242.         return this.dataItem.origin;
  7243.       } else {
  7244.         return loadTimeData.getStringF('embeddedOnHost',
  7245.                                        this.dataItem.embeddingOrigin);
  7246.       }
  7247.  
  7248.       return this.dataItem.displayPattern;
  7249.     },
  7250.     set pattern(pattern) {
  7251.       if (!this.editable)
  7252.         console.error('Tried to change uneditable pattern');
  7253.  
  7254.       this.dataItem.displayPattern = pattern;
  7255.     },
  7256.  
  7257.     /**
  7258.      * The setting (allow/block) for the exception.
  7259.      *
  7260.      * @type {string}
  7261.      */
  7262.     get setting() {
  7263.       return this.dataItem.setting;
  7264.     },
  7265.     set setting(setting) {
  7266.       this.dataItem.setting = setting;
  7267.     },
  7268.  
  7269.     /**
  7270.      * Gets a human-readable setting string.
  7271.      *
  7272.      * @return {string} The display string.
  7273.      */
  7274.     settingForDisplay: function() {
  7275.       return this.getDisplayStringForSetting(this.setting);
  7276.     },
  7277.  
  7278.     /**
  7279.      * media video specific function.
  7280.      * Gets a human-readable video setting string.
  7281.      *
  7282.      * @return {string} The display string.
  7283.      */
  7284.     videoSettingForDisplay: function() {
  7285.       return this.getDisplayStringForSetting(this.dataItem.video);
  7286.     },
  7287.  
  7288.     /**
  7289.      * Gets a human-readable display string for setting.
  7290.      *
  7291.      * @param {string} setting The setting to be displayed.
  7292.      * @return {string} The display string.
  7293.      */
  7294.     getDisplayStringForSetting: function(setting) {
  7295.       if (setting == 'allow')
  7296.         return loadTimeData.getString('allowException');
  7297.       else if (setting == 'block')
  7298.         return loadTimeData.getString('blockException');
  7299.       else if (setting == 'ask')
  7300.         return loadTimeData.getString('askException');
  7301.       else if (setting == 'session')
  7302.         return loadTimeData.getString('sessionException');
  7303.       else if (setting == 'default')
  7304.         return '';
  7305.  
  7306.       console.error('Unknown setting: [' + setting + ']');
  7307.       return '';
  7308.     },
  7309.  
  7310.     /**
  7311.      * Update this list item to reflect whether the input is a valid pattern.
  7312.      *
  7313.      * @param {boolean} valid Whether said pattern is valid in the context of a
  7314.      *     content exception setting.
  7315.      */
  7316.     setPatternValid: function(valid) {
  7317.       if (valid || !this.input.value)
  7318.         this.input.setCustomValidity('');
  7319.       else
  7320.         this.input.setCustomValidity(' ');
  7321.       this.inputIsValid = valid;
  7322.       this.inputValidityKnown = true;
  7323.     },
  7324.  
  7325.     /**
  7326.      * Set the <input> to its original contents. Used when the user quits
  7327.      * editing.
  7328.      */
  7329.     resetInput: function() {
  7330.       this.input.value = this.pattern;
  7331.     },
  7332.  
  7333.     /**
  7334.      * Copy the data model values to the editable nodes.
  7335.      */
  7336.     updateEditables: function() {
  7337.       this.resetInput();
  7338.  
  7339.       var settingOption =
  7340.           this.select.querySelector('[value=\'' + this.setting + '\']');
  7341.       if (settingOption)
  7342.         settingOption.selected = true;
  7343.     },
  7344.  
  7345.     /** @override */
  7346.     get currentInputIsValid() {
  7347.       return this.inputValidityKnown && this.inputIsValid;
  7348.     },
  7349.  
  7350.     /** @override */
  7351.     get hasBeenEdited() {
  7352.       var livePattern = this.input.value;
  7353.       var liveSetting = this.select.value;
  7354.       return livePattern != this.pattern || liveSetting != this.setting;
  7355.     },
  7356.  
  7357.     /**
  7358.      * Called when committing an edit.
  7359.      *
  7360.      * @param {Event} e The end event.
  7361.      * @private
  7362.      */
  7363.     onEditCommitted_: function(e) {
  7364.       var newPattern = this.input.value;
  7365.       var newSetting = this.select.value;
  7366.  
  7367.       this.finishEdit(newPattern, newSetting);
  7368.     },
  7369.  
  7370.     /**
  7371.      * Called when cancelling an edit; resets the control states.
  7372.      *
  7373.      * @param {Event} e The cancel event.
  7374.      * @private
  7375.      */
  7376.     onEditCancelled_: function() {
  7377.       this.updateEditables();
  7378.       this.setPatternValid(true);
  7379.     },
  7380.  
  7381.     /**
  7382.      * Editing is complete; update the model.
  7383.      *
  7384.      * @param {string} newPattern The pattern that the user entered.
  7385.      * @param {string} newSetting The setting the user chose.
  7386.      */
  7387.     finishEdit: function(newPattern, newSetting) {
  7388.       this.patternLabel.textContent = newPattern;
  7389.       this.settingLabel.textContent = this.settingForDisplay();
  7390.       var oldPattern = this.pattern;
  7391.       this.pattern = newPattern;
  7392.       this.setting = newSetting;
  7393.  
  7394.       // TODO(estade): this will need to be updated if geolocation/notifications
  7395.       // become editable.
  7396.       if (oldPattern != newPattern) {
  7397.         chrome.send('removeException',
  7398.                     [this.contentType, this.mode, oldPattern]);
  7399.       }
  7400.  
  7401.       chrome.send('setException',
  7402.                   [this.contentType, this.mode, newPattern, newSetting]);
  7403.     }
  7404.   };
  7405.  
  7406.   /**
  7407.    * Creates a new list item for the Add New Item row, which doesn't represent
  7408.    * an actual entry in the exceptions list but allows the user to add new
  7409.    * exceptions.
  7410.    *
  7411.    * @param {string} contentType The type of the list.
  7412.    * @param {string} mode The browser mode, 'otr' or 'normal'.
  7413.    * @param {boolean} enableAskOption Whether to show an 'ask every time' option
  7414.    *     in the select.
  7415.    * @constructor
  7416.    * @extends {cr.ui.ExceptionsListItem}
  7417.    */
  7418.   function ExceptionsAddRowListItem(contentType, mode, enableAskOption) {
  7419.     var el = cr.doc.createElement('div');
  7420.     el.mode = mode;
  7421.     el.contentType = contentType;
  7422.     el.enableAskOption = enableAskOption;
  7423.     el.dataItem = [];
  7424.     el.__proto__ = ExceptionsAddRowListItem.prototype;
  7425.     el.decorate();
  7426.  
  7427.     return el;
  7428.   }
  7429.  
  7430.   ExceptionsAddRowListItem.prototype = {
  7431.     __proto__: ExceptionsListItem.prototype,
  7432.  
  7433.     decorate: function() {
  7434.       ExceptionsListItem.prototype.decorate.call(this);
  7435.  
  7436.       this.input.placeholder =
  7437.           loadTimeData.getString('addNewExceptionInstructions');
  7438.  
  7439.       // Do we always want a default of allow?
  7440.       this.setting = 'allow';
  7441.     },
  7442.  
  7443.     /**
  7444.      * Clear the <input> and let the placeholder text show again.
  7445.      */
  7446.     resetInput: function() {
  7447.       this.input.value = '';
  7448.     },
  7449.  
  7450.     /** @override */
  7451.     get hasBeenEdited() {
  7452.       return this.input.value != '';
  7453.     },
  7454.  
  7455.     /**
  7456.      * Editing is complete; update the model. As long as the pattern isn't
  7457.      * empty, we'll just add it.
  7458.      *
  7459.      * @param {string} newPattern The pattern that the user entered.
  7460.      * @param {string} newSetting The setting the user chose.
  7461.      */
  7462.     finishEdit: function(newPattern, newSetting) {
  7463.       this.resetInput();
  7464.       chrome.send('setException',
  7465.                   [this.contentType, this.mode, newPattern, newSetting]);
  7466.     },
  7467.   };
  7468.  
  7469.   /**
  7470.    * Creates a new exceptions list.
  7471.    *
  7472.    * @constructor
  7473.    * @extends {cr.ui.List}
  7474.    */
  7475.   var ExceptionsList = cr.ui.define('list');
  7476.  
  7477.   ExceptionsList.prototype = {
  7478.     __proto__: InlineEditableItemList.prototype,
  7479.  
  7480.     /**
  7481.      * Called when an element is decorated as a list.
  7482.      */
  7483.     decorate: function() {
  7484.       InlineEditableItemList.prototype.decorate.call(this);
  7485.  
  7486.       this.classList.add('settings-list');
  7487.  
  7488.       for (var parentNode = this.parentNode; parentNode;
  7489.            parentNode = parentNode.parentNode) {
  7490.         if (parentNode.hasAttribute('contentType')) {
  7491.           this.contentType = parentNode.getAttribute('contentType');
  7492.           break;
  7493.         }
  7494.       }
  7495.  
  7496.       this.mode = this.getAttribute('mode');
  7497.  
  7498.       // Whether the exceptions in this list allow an 'Ask every time' option.
  7499.       this.enableAskOption = this.contentType == 'plugins';
  7500.  
  7501.       this.autoExpands = true;
  7502.       this.reset();
  7503.     },
  7504.  
  7505.     /**
  7506.      * Creates an item to go in the list.
  7507.      *
  7508.      * @param {Object} entry The element from the data model for this row.
  7509.      */
  7510.     createItem: function(entry) {
  7511.       if (entry) {
  7512.         return new ExceptionsListItem(this.contentType,
  7513.                                       this.mode,
  7514.                                       this.enableAskOption,
  7515.                                       entry);
  7516.       } else {
  7517.         var addRowItem = new ExceptionsAddRowListItem(this.contentType,
  7518.                                                       this.mode,
  7519.                                                       this.enableAskOption);
  7520.         addRowItem.deletable = false;
  7521.         return addRowItem;
  7522.       }
  7523.     },
  7524.  
  7525.     /**
  7526.      * Sets the exceptions in the js model.
  7527.      *
  7528.      * @param {Object} entries A list of dictionaries of values, each dictionary
  7529.      *     represents an exception.
  7530.      */
  7531.     setExceptions: function(entries) {
  7532.       var deleteCount = this.dataModel.length;
  7533.  
  7534.       if (this.isEditable()) {
  7535.         // We don't want to remove the Add New Exception row.
  7536.         deleteCount = deleteCount - 1;
  7537.       }
  7538.  
  7539.       var args = [0, deleteCount];
  7540.       args.push.apply(args, entries);
  7541.       this.dataModel.splice.apply(this.dataModel, args);
  7542.     },
  7543.  
  7544.     /**
  7545.      * The browser has finished checking a pattern for validity. Update the list
  7546.      * item to reflect this.
  7547.      *
  7548.      * @param {string} pattern The pattern.
  7549.      * @param {bool} valid Whether said pattern is valid in the context of a
  7550.      *     content exception setting.
  7551.      */
  7552.     patternValidityCheckComplete: function(pattern, valid) {
  7553.       var listItems = this.items;
  7554.       for (var i = 0; i < listItems.length; i++) {
  7555.         var listItem = listItems[i];
  7556.         // Don't do anything for messages for the item if it is not the intended
  7557.         // recipient, or if the response is stale (i.e. the input value has
  7558.         // changed since we sent the request to analyze it).
  7559.         if (pattern == listItem.input.value)
  7560.           listItem.setPatternValid(valid);
  7561.       }
  7562.     },
  7563.  
  7564.     /**
  7565.      * Returns whether the rows are editable in this list.
  7566.      */
  7567.     isEditable: function() {
  7568.       // Exceptions of the following lists are not editable for now.
  7569.       return !(this.contentType == 'notifications' ||
  7570.                this.contentType == 'location' ||
  7571.                this.contentType == 'fullscreen' ||
  7572.                this.contentType == 'media-stream');
  7573.     },
  7574.  
  7575.     /**
  7576.      * Removes all exceptions from the js model.
  7577.      */
  7578.     reset: function() {
  7579.       if (this.isEditable()) {
  7580.         // The null creates the Add New Exception row.
  7581.         this.dataModel = new ArrayDataModel([null]);
  7582.       } else {
  7583.         this.dataModel = new ArrayDataModel([]);
  7584.       }
  7585.     },
  7586.  
  7587.     /** @override */
  7588.     deleteItemAtIndex: function(index) {
  7589.       var listItem = this.getListItemByIndex(index);
  7590.       if (!listItem.deletable)
  7591.         return;
  7592.  
  7593.       var dataItem = listItem.dataItem;
  7594.       var args = [listItem.contentType];
  7595.       if (listItem.contentType == 'notifications')
  7596.         args.push(dataItem.origin, dataItem.setting);
  7597.       else
  7598.         args.push(listItem.mode, dataItem.origin, dataItem.embeddingOrigin);
  7599.  
  7600.       chrome.send('removeException', args);
  7601.     },
  7602.   };
  7603.  
  7604.   var OptionsPage = options.OptionsPage;
  7605.  
  7606.   /**
  7607.    * Encapsulated handling of content settings list subpage.
  7608.    *
  7609.    * @constructor
  7610.    */
  7611.   function ContentSettingsExceptionsArea() {
  7612.     OptionsPage.call(this, 'contentExceptions',
  7613.                      loadTimeData.getString('contentSettingsPageTabTitle'),
  7614.                      'content-settings-exceptions-area');
  7615.   }
  7616.  
  7617.   cr.addSingletonGetter(ContentSettingsExceptionsArea);
  7618.  
  7619.   ContentSettingsExceptionsArea.prototype = {
  7620.     __proto__: OptionsPage.prototype,
  7621.  
  7622.     initializePage: function() {
  7623.       OptionsPage.prototype.initializePage.call(this);
  7624.  
  7625.       var exceptionsLists = this.pageDiv.querySelectorAll('list');
  7626.       for (var i = 0; i < exceptionsLists.length; i++) {
  7627.         options.contentSettings.ExceptionsList.decorate(exceptionsLists[i]);
  7628.       }
  7629.  
  7630.       ContentSettingsExceptionsArea.hideOTRLists(false);
  7631.  
  7632.       // If the user types in the URL without a hash, show just cookies.
  7633.       this.showList('cookies');
  7634.  
  7635.       $('content-settings-exceptions-overlay-confirm').onclick =
  7636.           OptionsPage.closeOverlay.bind(OptionsPage);
  7637.     },
  7638.  
  7639.     /**
  7640.      * Shows one list and hides all others.
  7641.      *
  7642.      * @param {string} type The content type.
  7643.      */
  7644.     showList: function(type) {
  7645.       var header = this.pageDiv.querySelector('h1');
  7646.       header.textContent = loadTimeData.getString(type + '_header');
  7647.  
  7648.       var divs = this.pageDiv.querySelectorAll('div[contentType]');
  7649.       for (var i = 0; i < divs.length; i++) {
  7650.         if (divs[i].getAttribute('contentType') == type)
  7651.           divs[i].hidden = false;
  7652.         else
  7653.           divs[i].hidden = true;
  7654.       }
  7655.  
  7656.       var media_header = this.pageDiv.querySelector('.media-header');
  7657.       media_header.hidden = type != 'media-stream';
  7658.     },
  7659.  
  7660.     /**
  7661.      * Called after the page has been shown. Show the content type for the
  7662.      * location's hash.
  7663.      */
  7664.     didShowPage: function() {
  7665.       var hash = location.hash;
  7666.       if (hash)
  7667.         this.showList(hash.slice(1));
  7668.     },
  7669.   };
  7670.  
  7671.   /**
  7672.    * Called when the last incognito window is closed.
  7673.    */
  7674.   ContentSettingsExceptionsArea.OTRProfileDestroyed = function() {
  7675.     this.hideOTRLists(true);
  7676.   };
  7677.  
  7678.   /**
  7679.    * Hides the incognito exceptions lists and optionally clears them as well.
  7680.    * @param {boolean} clear Whether to clear the lists.
  7681.    */
  7682.   ContentSettingsExceptionsArea.hideOTRLists = function(clear) {
  7683.     var otrLists = document.querySelectorAll('list[mode=otr]');
  7684.  
  7685.     for (var i = 0; i < otrLists.length; i++) {
  7686.       otrLists[i].parentNode.hidden = true;
  7687.       if (clear)
  7688.         otrLists[i].reset();
  7689.     }
  7690.   };
  7691.  
  7692.   return {
  7693.     ExceptionsListItem: ExceptionsListItem,
  7694.     ExceptionsAddRowListItem: ExceptionsAddRowListItem,
  7695.     ExceptionsList: ExceptionsList,
  7696.     ContentSettingsExceptionsArea: ContentSettingsExceptionsArea,
  7697.   };
  7698. });
  7699.  
  7700. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  7701. // Use of this source code is governed by a BSD-style license that can be
  7702. // found in the LICENSE file.
  7703.  
  7704. cr.define('options', function() {
  7705.  
  7706.   //////////////////////////////////////////////////////////////////////////////
  7707.   // ContentSettingsRadio class:
  7708.  
  7709.   // Define a constructor that uses an input element as its underlying element.
  7710.   var ContentSettingsRadio = cr.ui.define('input');
  7711.  
  7712.   ContentSettingsRadio.prototype = {
  7713.     __proto__: HTMLInputElement.prototype,
  7714.  
  7715.     /**
  7716.      * Initialization function for the cr.ui framework.
  7717.      */
  7718.     decorate: function() {
  7719.       this.type = 'radio';
  7720.       var self = this;
  7721.  
  7722.       this.addEventListener('change',
  7723.           function(e) {
  7724.             chrome.send('setContentFilter', [this.name, this.value]);
  7725.           });
  7726.     },
  7727.   };
  7728.  
  7729.   /**
  7730.    * Whether the content setting is controlled by something else than the user's
  7731.    * settings (either 'policy' or 'extension').
  7732.    * @type {string}
  7733.    */
  7734.   cr.defineProperty(ContentSettingsRadio, 'controlledBy', cr.PropertyKind.ATTR);
  7735.  
  7736.   //////////////////////////////////////////////////////////////////////////////
  7737.   // HandlersEnabledRadio class:
  7738.  
  7739.   // Define a constructor that uses an input element as its underlying element.
  7740.   var HandlersEnabledRadio = cr.ui.define('input');
  7741.  
  7742.   HandlersEnabledRadio.prototype = {
  7743.     __proto__: HTMLInputElement.prototype,
  7744.  
  7745.     /**
  7746.      * Initialization function for the cr.ui framework.
  7747.      */
  7748.     decorate: function() {
  7749.       this.type = 'radio';
  7750.       var self = this;
  7751.  
  7752.       this.addEventListener('change',
  7753.           function(e) {
  7754.             chrome.send('setHandlersEnabled', [this.value == 'allow']);
  7755.           });
  7756.     },
  7757.   };
  7758.  
  7759.   // Export
  7760.   return {
  7761.     ContentSettingsRadio: ContentSettingsRadio,
  7762.     HandlersEnabledRadio: HandlersEnabledRadio
  7763.   };
  7764.  
  7765. });
  7766.  
  7767.  
  7768. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  7769. // Use of this source code is governed by a BSD-style license that can be
  7770. // found in the LICENSE file.
  7771.  
  7772. cr.define('options', function() {
  7773.   /** @const */ var DeletableItemList = options.DeletableItemList;
  7774.   /** @const */ var DeletableItem = options.DeletableItem;
  7775.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  7776.   /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  7777.  
  7778.   // This structure maps the various cookie type names from C++ (hence the
  7779.   // underscores) to arrays of the different types of data each has, along with
  7780.   // the i18n name for the description of that data type.
  7781.   /** @const */ var cookieInfo = {
  7782.     'cookie': [['name', 'label_cookie_name'],
  7783.                ['content', 'label_cookie_content'],
  7784.                ['domain', 'label_cookie_domain'],
  7785.                ['path', 'label_cookie_path'],
  7786.                ['sendfor', 'label_cookie_send_for'],
  7787.                ['accessibleToScript', 'label_cookie_accessible_to_script'],
  7788.                ['created', 'label_cookie_created'],
  7789.                ['expires', 'label_cookie_expires']],
  7790.     'app_cache': [['manifest', 'label_app_cache_manifest'],
  7791.                   ['size', 'label_local_storage_size'],
  7792.                   ['created', 'label_cookie_created'],
  7793.                   ['accessed', 'label_cookie_last_accessed']],
  7794.     'database': [['name', 'label_cookie_name'],
  7795.                  ['desc', 'label_webdb_desc'],
  7796.                  ['size', 'label_local_storage_size'],
  7797.                  ['modified', 'label_local_storage_last_modified']],
  7798.     'local_storage': [['origin', 'label_local_storage_origin'],
  7799.                       ['size', 'label_local_storage_size'],
  7800.                       ['modified', 'label_local_storage_last_modified']],
  7801.     'indexed_db': [['origin', 'label_indexed_db_origin'],
  7802.                    ['size', 'label_indexed_db_size'],
  7803.                    ['modified', 'label_indexed_db_last_modified']],
  7804.     'file_system': [['origin', 'label_file_system_origin'],
  7805.                     ['persistent', 'label_file_system_persistent_usage'],
  7806.                     ['temporary', 'label_file_system_temporary_usage']],
  7807.     'server_bound_cert': [['serverId', 'label_server_bound_cert_server_id'],
  7808.                           ['certType', 'label_server_bound_cert_type'],
  7809.                           ['created', 'label_server_bound_cert_created'],
  7810.                           ['expires', 'label_server_bound_cert_expires']],
  7811.     'flash_lso': [['domain', 'label_cookie_domain']],
  7812.   };
  7813.  
  7814.   /**
  7815.    * Returns the item's height, like offsetHeight but such that it works better
  7816.    * when the page is zoomed. See the similar calculation in @{code cr.ui.List}.
  7817.    * This version also accounts for the animation done in this file.
  7818.    * @param {Element} item The item to get the height of.
  7819.    * @return {number} The height of the item, calculated with zooming in mind.
  7820.    */
  7821.   function getItemHeight(item) {
  7822.     var height = item.style.height;
  7823.     // Use the fixed animation target height if set, in case the element is
  7824.     // currently being animated and we'd get an intermediate height below.
  7825.     if (height && height.substr(-2) == 'px')
  7826.       return parseInt(height.substr(0, height.length - 2));
  7827.     return item.getBoundingClientRect().height;
  7828.   }
  7829.  
  7830.   /**
  7831.    * Create tree nodes for the objects in the data array, and insert them all
  7832.    * into the given list using its @{code splice} method at the given index.
  7833.    * @param {Array.<Object>} data The data objects for the nodes to add.
  7834.    * @param {number} start The index at which to start inserting the nodes.
  7835.    * @return {Array.<CookieTreeNode>} An array of CookieTreeNodes added.
  7836.    */
  7837.   function spliceTreeNodes(data, start, list) {
  7838.     var nodes = data.map(function(x) { return new CookieTreeNode(x); });
  7839.     // Insert [start, 0] at the beginning of the array of nodes, making it
  7840.     // into the arguments we want to pass to @{code list.splice} below.
  7841.     nodes.splice(0, 0, start, 0);
  7842.     list.splice.apply(list, nodes);
  7843.     // Remove the [start, 0] prefix and return the array of nodes.
  7844.     nodes.splice(0, 2);
  7845.     return nodes;
  7846.   }
  7847.  
  7848.   /**
  7849.    * Adds information about an app that protects this data item to the
  7850.    * @{code element}.
  7851.    * @param {Element} element The DOM element the information should be
  7852.          appended to.
  7853.    * @param {{id: string, name: string}} appInfo Information about an app.
  7854.    */
  7855.   function addAppInfo(element, appInfo) {
  7856.     var img = element.ownerDocument.createElement('img');
  7857.     img.src = 'chrome://extension-icon/' + appInfo.id + '/16/1';
  7858.     element.title = loadTimeData.getString('label_protected_by_apps') +
  7859.                     ' ' + appInfo.name;
  7860.     img.className = 'protecting-app';
  7861.     element.appendChild(img);
  7862.   }
  7863.  
  7864.   var parentLookup = {};
  7865.   var lookupRequests = {};
  7866.  
  7867.   /**
  7868.    * Creates a new list item for sites data. Note that these are created and
  7869.    * destroyed lazily as they scroll into and out of view, so they must be
  7870.    * stateless. We cache the expanded item in @{code CookiesList} though, so it
  7871.    * can keep state. (Mostly just which item is selected.)
  7872.    * @param {Object} origin Data used to create a cookie list item.
  7873.    * @param {CookiesList} list The list that will contain this item.
  7874.    * @constructor
  7875.    * @extends {DeletableItem}
  7876.    */
  7877.   function CookieListItem(origin, list) {
  7878.     var listItem = new DeletableItem(null);
  7879.     listItem.__proto__ = CookieListItem.prototype;
  7880.  
  7881.     listItem.origin = origin;
  7882.     listItem.list = list;
  7883.     listItem.decorate();
  7884.  
  7885.     // This hooks up updateOrigin() to the list item, makes the top-level
  7886.     // tree nodes (i.e., origins) register their IDs in parentLookup, and
  7887.     // causes them to request their children if they have none. Note that we
  7888.     // have special logic in the setter for the parent property to make sure
  7889.     // that we can still garbage collect list items when they scroll out of
  7890.     // view, even though it appears that we keep a direct reference.
  7891.     if (origin) {
  7892.       origin.parent = listItem;
  7893.       origin.updateOrigin();
  7894.     }
  7895.  
  7896.     return listItem;
  7897.   }
  7898.  
  7899.   CookieListItem.prototype = {
  7900.     __proto__: DeletableItem.prototype,
  7901.  
  7902.     /** @override */
  7903.     decorate: function() {
  7904.       this.siteChild = this.ownerDocument.createElement('div');
  7905.       this.siteChild.className = 'cookie-site';
  7906.       this.dataChild = this.ownerDocument.createElement('div');
  7907.       this.dataChild.className = 'cookie-data';
  7908.       this.sizeChild = this.ownerDocument.createElement('div');
  7909.       this.sizeChild.className = 'cookie-size';
  7910.       this.itemsChild = this.ownerDocument.createElement('div');
  7911.       this.itemsChild.className = 'cookie-items';
  7912.       this.infoChild = this.ownerDocument.createElement('div');
  7913.       this.infoChild.className = 'cookie-details';
  7914.       this.infoChild.hidden = true;
  7915.  
  7916.       var remove = this.ownerDocument.createElement('button');
  7917.       remove.textContent = loadTimeData.getString('remove_cookie');
  7918.       remove.onclick = this.removeCookie_.bind(this);
  7919.       this.infoChild.appendChild(remove);
  7920.       var content = this.contentElement;
  7921.       content.appendChild(this.siteChild);
  7922.       content.appendChild(this.dataChild);
  7923.       content.appendChild(this.sizeChild);
  7924.       content.appendChild(this.itemsChild);
  7925.       this.itemsChild.appendChild(this.infoChild);
  7926.       if (this.origin && this.origin.data) {
  7927.         this.siteChild.textContent = this.origin.data.title;
  7928.         this.siteChild.setAttribute('title', this.origin.data.title);
  7929.       }
  7930.       this.itemList_ = [];
  7931.     },
  7932.  
  7933.     /** @type {boolean} */
  7934.     get expanded() {
  7935.       return this.expanded_;
  7936.     },
  7937.     set expanded(expanded) {
  7938.       if (this.expanded_ == expanded)
  7939.         return;
  7940.       this.expanded_ = expanded;
  7941.       if (expanded) {
  7942.         var oldExpanded = this.list.expandedItem;
  7943.         this.list.expandedItem = this;
  7944.         this.updateItems_();
  7945.         if (oldExpanded)
  7946.           oldExpanded.expanded = false;
  7947.         this.classList.add('show-items');
  7948.       } else {
  7949.         if (this.list.expandedItem == this) {
  7950.           this.list.expandedItem = null;
  7951.         }
  7952.         this.style.height = '';
  7953.         this.itemsChild.style.height = '';
  7954.         this.classList.remove('show-items');
  7955.       }
  7956.     },
  7957.  
  7958.     /**
  7959.      * The callback for the "remove" button shown when an item is selected.
  7960.      * Requests that the currently selected cookie be removed.
  7961.      * @private
  7962.      */
  7963.     removeCookie_: function() {
  7964.       if (this.selectedIndex_ >= 0) {
  7965.         var item = this.itemList_[this.selectedIndex_];
  7966.         if (item && item.node)
  7967.           chrome.send('removeCookie', [item.node.pathId]);
  7968.       }
  7969.     },
  7970.  
  7971.     /**
  7972.      * Disable animation within this cookie list item, in preparation for making
  7973.      * changes that will need to be animated. Makes it possible to measure the
  7974.      * contents without displaying them, to set animation targets.
  7975.      * @private
  7976.      */
  7977.     disableAnimation_: function() {
  7978.       this.itemsHeight_ = getItemHeight(this.itemsChild);
  7979.       this.classList.add('measure-items');
  7980.     },
  7981.  
  7982.     /**
  7983.      * Enable animation after changing the contents of this cookie list item.
  7984.      * See @{code disableAnimation_}.
  7985.      * @private
  7986.      */
  7987.     enableAnimation_: function() {
  7988.       if (!this.classList.contains('measure-items'))
  7989.         this.disableAnimation_();
  7990.       this.itemsChild.style.height = '';
  7991.       // This will force relayout in order to calculate the new heights.
  7992.       var itemsHeight = getItemHeight(this.itemsChild);
  7993.       var fixedHeight = getItemHeight(this) + itemsHeight - this.itemsHeight_;
  7994.       this.itemsChild.style.height = this.itemsHeight_ + 'px';
  7995.       // Force relayout before enabling animation, so that if we have
  7996.       // changed things since the last layout, they will not be animated
  7997.       // during subsequent layouts.
  7998.       this.itemsChild.offsetHeight;
  7999.       this.classList.remove('measure-items');
  8000.       this.itemsChild.style.height = itemsHeight + 'px';
  8001.       this.style.height = fixedHeight + 'px';
  8002.     },
  8003.  
  8004.     /**
  8005.      * Updates the origin summary to reflect changes in its items.
  8006.      * Both CookieListItem and CookieTreeNode implement this API.
  8007.      * This implementation scans the descendants to update the text.
  8008.      */
  8009.     updateOrigin: function() {
  8010.       var info = {
  8011.         cookies: 0,
  8012.         database: false,
  8013.         localStorage: false,
  8014.         appCache: false,
  8015.         indexedDb: false,
  8016.         fileSystem: false,
  8017.         serverBoundCerts: 0,
  8018.       };
  8019.       if (this.origin)
  8020.         this.origin.collectSummaryInfo(info);
  8021.  
  8022.       var list = [];
  8023.       if (info.cookies > 1)
  8024.         list.push(loadTimeData.getStringF('cookie_plural', info.cookies));
  8025.       else if (info.cookies > 0)
  8026.         list.push(loadTimeData.getString('cookie_singular'));
  8027.       if (info.database || info.indexedDb)
  8028.         list.push(loadTimeData.getString('cookie_database_storage'));
  8029.       if (info.localStorage)
  8030.         list.push(loadTimeData.getString('cookie_local_storage'));
  8031.       if (info.appCache)
  8032.         list.push(loadTimeData.getString('cookie_app_cache'));
  8033.       if (info.fileSystem)
  8034.         list.push(loadTimeData.getString('cookie_file_system'));
  8035.       if (info.serverBoundCerts)
  8036.         list.push(loadTimeData.getString('cookie_server_bound_cert'));
  8037.       if (info.flashLSO)
  8038.         list.push(loadTimeData.getString('cookie_flash_lso'));
  8039.  
  8040.       var text = '';
  8041.       for (var i = 0; i < list.length; ++i) {
  8042.         if (text.length > 0)
  8043.           text += ', ' + list[i];
  8044.         else
  8045.           text = list[i];
  8046.       }
  8047.       this.dataChild.textContent = text;
  8048.  
  8049.       for (var key in info.appsProtectingThis) {
  8050.         addAppInfo(this.dataChild, apps[key]);
  8051.       }
  8052.  
  8053.       if (info.quota && info.quota.totalUsage)
  8054.         this.sizeChild.textContent = info.quota.totalUsage;
  8055.  
  8056.       if (this.expanded)
  8057.         this.updateItems_();
  8058.     },
  8059.  
  8060.     /**
  8061.      * Updates the items section to reflect changes, animating to the new state.
  8062.      * Removes existing contents and calls @{code CookieTreeNode.createItems}.
  8063.      * @private
  8064.      */
  8065.     updateItems_: function() {
  8066.       this.disableAnimation_();
  8067.       this.itemsChild.textContent = '';
  8068.       this.infoChild.hidden = true;
  8069.       this.selectedIndex_ = -1;
  8070.       this.itemList_ = [];
  8071.       if (this.origin)
  8072.         this.origin.createItems(this);
  8073.       this.itemsChild.appendChild(this.infoChild);
  8074.       this.enableAnimation_();
  8075.     },
  8076.  
  8077.     /**
  8078.      * Append a new cookie node "bubble" to this list item.
  8079.      * @param {CookieTreeNode} node The cookie node to add a bubble for.
  8080.      * @param {Element} div The DOM element for the bubble itself.
  8081.      * @return {number} The index the bubble was added at.
  8082.      */
  8083.     appendItem: function(node, div) {
  8084.       this.itemList_.push({node: node, div: div});
  8085.       this.itemsChild.appendChild(div);
  8086.       return this.itemList_.length - 1;
  8087.     },
  8088.  
  8089.     /**
  8090.      * The currently selected cookie node ("cookie bubble") index.
  8091.      * @type {number}
  8092.      * @private
  8093.      */
  8094.     selectedIndex_: -1,
  8095.  
  8096.     /**
  8097.      * Get the currently selected cookie node ("cookie bubble") index.
  8098.      * @type {number}
  8099.      */
  8100.     get selectedIndex() {
  8101.       return this.selectedIndex_;
  8102.     },
  8103.  
  8104.     /**
  8105.      * Set the currently selected cookie node ("cookie bubble") index to
  8106.      * @{code itemIndex}, unselecting any previously selected node first.
  8107.      * @param {number} itemIndex The index to set as the selected index.
  8108.      */
  8109.     set selectedIndex(itemIndex) {
  8110.       // Get the list index up front before we change anything.
  8111.       var index = this.list.getIndexOfListItem(this);
  8112.       // Unselect any previously selected item.
  8113.       if (this.selectedIndex_ >= 0) {
  8114.         var item = this.itemList_[this.selectedIndex_];
  8115.         if (item && item.div)
  8116.           item.div.removeAttribute('selected');
  8117.       }
  8118.       // Special case: decrementing -1 wraps around to the end of the list.
  8119.       if (itemIndex == -2)
  8120.         itemIndex = this.itemList_.length - 1;
  8121.       // Check if we're going out of bounds and hide the item details.
  8122.       if (itemIndex < 0 || itemIndex >= this.itemList_.length) {
  8123.         this.selectedIndex_ = -1;
  8124.         this.disableAnimation_();
  8125.         this.infoChild.hidden = true;
  8126.         this.enableAnimation_();
  8127.         return;
  8128.       }
  8129.       // Set the new selected item and show the item details for it.
  8130.       this.selectedIndex_ = itemIndex;
  8131.       this.itemList_[itemIndex].div.setAttribute('selected', '');
  8132.       this.disableAnimation_();
  8133.       this.itemList_[itemIndex].node.setDetailText(this.infoChild,
  8134.                                                    this.list.infoNodes);
  8135.       this.infoChild.hidden = false;
  8136.       this.enableAnimation_();
  8137.       // If we're near the bottom of the list this may cause the list item to go
  8138.       // beyond the end of the visible area. Fix it after the animation is done.
  8139.       var list = this.list;
  8140.       window.setTimeout(function() { list.scrollIndexIntoView(index); }, 150);
  8141.     },
  8142.   };
  8143.  
  8144.   /**
  8145.    * {@code CookieTreeNode}s mirror the structure of the cookie tree lazily, and
  8146.    * contain all the actual data used to generate the {@code CookieListItem}s.
  8147.    * @param {Object} data The data object for this node.
  8148.    * @constructor
  8149.    */
  8150.   function CookieTreeNode(data) {
  8151.     this.data = data;
  8152.     this.children = [];
  8153.   }
  8154.  
  8155.   CookieTreeNode.prototype = {
  8156.     /**
  8157.      * Insert the given list of cookie tree nodes at the given index.
  8158.      * Both CookiesList and CookieTreeNode implement this API.
  8159.      * @param {Array.<Object>} data The data objects for the nodes to add.
  8160.      * @param {number} start The index at which to start inserting the nodes.
  8161.      */
  8162.     insertAt: function(data, start) {
  8163.       var nodes = spliceTreeNodes(data, start, this.children);
  8164.       for (var i = 0; i < nodes.length; i++)
  8165.         nodes[i].parent = this;
  8166.       this.updateOrigin();
  8167.     },
  8168.  
  8169.     /**
  8170.      * Remove a cookie tree node from the given index.
  8171.      * Both CookiesList and CookieTreeNode implement this API.
  8172.      * @param {number} index The index of the tree node to remove.
  8173.      */
  8174.     remove: function(index) {
  8175.       if (index < this.children.length) {
  8176.         this.children.splice(index, 1);
  8177.         this.updateOrigin();
  8178.       }
  8179.     },
  8180.  
  8181.     /**
  8182.      * Clears all children.
  8183.      * Both CookiesList and CookieTreeNode implement this API.
  8184.      * It is used by CookiesList.loadChildren().
  8185.      */
  8186.     clear: function() {
  8187.       // We might leave some garbage in parentLookup for removed children.
  8188.       // But that should be OK because parentLookup is cleared when we
  8189.       // reload the tree.
  8190.       this.children = [];
  8191.       this.updateOrigin();
  8192.     },
  8193.  
  8194.     /**
  8195.      * The counter used by startBatchUpdates() and endBatchUpdates().
  8196.      * @type {number}
  8197.      */
  8198.     batchCount_: 0,
  8199.  
  8200.     /**
  8201.      * See cr.ui.List.startBatchUpdates().
  8202.      * Both CookiesList (via List) and CookieTreeNode implement this API.
  8203.      */
  8204.     startBatchUpdates: function() {
  8205.       this.batchCount_++;
  8206.     },
  8207.  
  8208.     /**
  8209.      * See cr.ui.List.endBatchUpdates().
  8210.      * Both CookiesList (via List) and CookieTreeNode implement this API.
  8211.      */
  8212.     endBatchUpdates: function() {
  8213.       if (!--this.batchCount_)
  8214.         this.updateOrigin();
  8215.     },
  8216.  
  8217.     /**
  8218.      * Requests updating the origin summary to reflect changes in this item.
  8219.      * Both CookieListItem and CookieTreeNode implement this API.
  8220.      */
  8221.     updateOrigin: function() {
  8222.       if (!this.batchCount_ && this.parent)
  8223.         this.parent.updateOrigin();
  8224.     },
  8225.  
  8226.     /**
  8227.      * Summarize the information in this node and update @{code info}.
  8228.      * This will recurse into child nodes to summarize all descendants.
  8229.      * @param {Object} info The info object from @{code updateOrigin}.
  8230.      */
  8231.     collectSummaryInfo: function(info) {
  8232.       if (this.children.length > 0) {
  8233.         for (var i = 0; i < this.children.length; ++i)
  8234.           this.children[i].collectSummaryInfo(info);
  8235.       } else if (this.data && !this.data.hasChildren) {
  8236.         if (this.data.type == 'cookie') {
  8237.           info.cookies++;
  8238.         } else if (this.data.type == 'database') {
  8239.           info.database = true;
  8240.         } else if (this.data.type == 'local_storage') {
  8241.           info.localStorage = true;
  8242.         } else if (this.data.type == 'app_cache') {
  8243.           info.appCache = true;
  8244.         } else if (this.data.type == 'indexed_db') {
  8245.           info.indexedDb = true;
  8246.         } else if (this.data.type == 'file_system') {
  8247.           info.fileSystem = true;
  8248.         } else if (this.data.type == 'quota') {
  8249.           info.quota = this.data;
  8250.         } else if (this.data.type == 'server_bound_cert') {
  8251.           info.serverBoundCerts++;
  8252.         } else if (this.data.type == 'flash_lso') {
  8253.           info.flashLSO = true;
  8254.         }
  8255.  
  8256.         var apps = this.data.appsProtectingThis;
  8257.         if (apps) {
  8258.           if (!info.appsProtectingThis)
  8259.             info.appsProtectingThis = {};
  8260.           apps.forEach(function(appInfo) {
  8261.             info.appsProtectingThis[appInfo.id] = appInfo;
  8262.           });
  8263.         }
  8264.       }
  8265.     },
  8266.  
  8267.     /**
  8268.      * Create the cookie "bubbles" for this node, recursing into children
  8269.      * if there are any. Append the cookie bubbles to @{code item}.
  8270.      * @param {CookieListItem} item The cookie list item to create items in.
  8271.      */
  8272.     createItems: function(item) {
  8273.       if (this.children.length > 0) {
  8274.         for (var i = 0; i < this.children.length; ++i)
  8275.           this.children[i].createItems(item);
  8276.         return;
  8277.       }
  8278.  
  8279.       if (!this.data || this.data.hasChildren)
  8280.         return;
  8281.  
  8282.       var text = '';
  8283.       switch (this.data.type) {
  8284.         case 'cookie':
  8285.         case 'database':
  8286.           text = this.data.name;
  8287.           break;
  8288.         default:
  8289.           text = loadTimeData.getString('cookie_' + this.data.type);
  8290.       }
  8291.       if (!text)
  8292.         return;
  8293.  
  8294.       var div = item.ownerDocument.createElement('div');
  8295.       div.className = 'cookie-item';
  8296.       // Help out screen readers and such: this is a clickable thing.
  8297.       div.setAttribute('role', 'button');
  8298.       div.tabIndex = 0;
  8299.       div.textContent = text;
  8300.       var apps = this.data.appsProtectingThis;
  8301.       if (apps)
  8302.         apps.forEach(addAppInfo.bind(null, div));
  8303.  
  8304.       var index = item.appendItem(this, div);
  8305.       div.onclick = function() {
  8306.         item.selectedIndex = (item.selectedIndex == index) ? -1 : index;
  8307.       };
  8308.     },
  8309.  
  8310.     /**
  8311.      * Set the detail text to be displayed to that of this cookie tree node.
  8312.      * Uses preallocated DOM elements for each cookie node type from @{code
  8313.      * infoNodes}, and inserts the appropriate elements to @{code element}.
  8314.      * @param {Element} element The DOM element to insert elements to.
  8315.      * @param {Object.<string, {table: Element, info: Object.<string,
  8316.      *     Element>}>} infoNodes The map from cookie node types to maps from
  8317.      *     cookie attribute names to DOM elements to display cookie attribute
  8318.      *     values, created by @{code CookiesList.decorate}.
  8319.      */
  8320.     setDetailText: function(element, infoNodes) {
  8321.       var table;
  8322.       if (this.data && !this.data.hasChildren && cookieInfo[this.data.type]) {
  8323.         var info = cookieInfo[this.data.type];
  8324.         var nodes = infoNodes[this.data.type].info;
  8325.         for (var i = 0; i < info.length; ++i) {
  8326.           var name = info[i][0];
  8327.           if (name != 'id' && this.data[name])
  8328.             nodes[name].textContent = this.data[name];
  8329.           else
  8330.             nodes[name].textContent = '';
  8331.         }
  8332.         table = infoNodes[this.data.type].table;
  8333.       }
  8334.  
  8335.       while (element.childNodes.length > 1)
  8336.         element.removeChild(element.firstChild);
  8337.  
  8338.       if (table)
  8339.         element.insertBefore(table, element.firstChild);
  8340.     },
  8341.  
  8342.     /**
  8343.      * The parent of this cookie tree node.
  8344.      * @type {?CookieTreeNode|CookieListItem}
  8345.      */
  8346.     get parent() {
  8347.       // See below for an explanation of this special case.
  8348.       if (typeof this.parent_ == 'number')
  8349.         return this.list_.getListItemByIndex(this.parent_);
  8350.       return this.parent_;
  8351.     },
  8352.     set parent(parent) {
  8353.       if (parent == this.parent)
  8354.         return;
  8355.  
  8356.       if (parent instanceof CookieListItem) {
  8357.         // If the parent is to be a CookieListItem, then we keep the reference
  8358.         // to it by its containing list and list index, rather than directly.
  8359.         // This allows the list items to be garbage collected when they scroll
  8360.         // out of view (except the expanded item, which we cache). This is
  8361.         // transparent except in the setter and getter, where we handle it.
  8362.         if (this.parent_ == undefined || parent.listIndex != -1) {
  8363.           // Setting the parent is somewhat tricky because the CookieListItem
  8364.           // constructor has side-effects on the |origin| that it wraps. Every
  8365.           // time a CookieListItem is created for an |origin|, it registers
  8366.           // itself as the parent of the |origin|.
  8367.           // The List implementation may create a temporary CookieListItem item
  8368.           // that wraps the |origin| of the very first entry of the CokiesList,
  8369.           // when the List is redrawn the first time. This temporary
  8370.           // CookieListItem is fresh (has listIndex = -1) and is never inserted
  8371.           // into the List. Therefore it gets never updated. This destroys the
  8372.           // chain of parent pointers.
  8373.           // This is the stack trace:
  8374.           //     CookieListItem
  8375.           //     CookiesList.createItem
  8376.           //     List.measureItem
  8377.           //     List.getDefaultItemSize_
  8378.           //     List.getDefaultItemHeight_
  8379.           //     List.getIndexForListOffset_
  8380.           //     List.getItemsInViewPort
  8381.           //     List.redraw
  8382.           //     List.endBatchUpdates
  8383.           //     CookiesList.loadChildren
  8384.           this.parent_ = parent.listIndex;
  8385.         }
  8386.         this.list_ = parent.list;
  8387.         parent.addEventListener('listIndexChange',
  8388.                                 this.parentIndexChanged_.bind(this));
  8389.       } else {
  8390.         this.parent_ = parent;
  8391.       }
  8392.  
  8393.       if (this.data && this.data.id) {
  8394.         if (parent)
  8395.           parentLookup[this.data.id] = this;
  8396.         else
  8397.           delete parentLookup[this.data.id];
  8398.       }
  8399.  
  8400.       if (this.data && this.data.hasChildren &&
  8401.           !this.children.length && !lookupRequests[this.data.id]) {
  8402.         lookupRequests[this.data.id] = true;
  8403.         chrome.send('loadCookie', [this.pathId]);
  8404.       }
  8405.     },
  8406.  
  8407.     /**
  8408.      * Called when the parent is a CookieListItem whose index has changed.
  8409.      * See the code above that avoids keeping a direct reference to
  8410.      * CookieListItem parents, to allow them to be garbage collected.
  8411.      * @private
  8412.      */
  8413.     parentIndexChanged_: function(event) {
  8414.       if (typeof this.parent_ == 'number') {
  8415.         this.parent_ = event.newValue;
  8416.         // We set a timeout to update the origin, rather than doing it right
  8417.         // away, because this callback may occur while the list items are
  8418.         // being repopulated following a scroll event. Calling updateOrigin()
  8419.         // immediately could trigger relayout that would reset the scroll
  8420.         // position within the list, among other things.
  8421.         window.setTimeout(this.updateOrigin.bind(this), 0);
  8422.       }
  8423.     },
  8424.  
  8425.     /**
  8426.      * The cookie tree path id.
  8427.      * @type {string}
  8428.      */
  8429.     get pathId() {
  8430.       var parent = this.parent;
  8431.       if (parent && parent instanceof CookieTreeNode)
  8432.         return parent.pathId + ',' + this.data.id;
  8433.       return this.data.id;
  8434.     },
  8435.   };
  8436.  
  8437.   /**
  8438.    * Creates a new cookies list.
  8439.    * @param {Object=} opt_propertyBag Optional properties.
  8440.    * @constructor
  8441.    * @extends {DeletableItemList}
  8442.    */
  8443.   var CookiesList = cr.ui.define('list');
  8444.  
  8445.   CookiesList.prototype = {
  8446.     __proto__: DeletableItemList.prototype,
  8447.  
  8448.     /** @override */
  8449.     decorate: function() {
  8450.       DeletableItemList.prototype.decorate.call(this);
  8451.       this.classList.add('cookie-list');
  8452.       this.dataModel = new ArrayDataModel([]);
  8453.       this.addEventListener('keydown', this.handleKeyLeftRight_.bind(this));
  8454.       var sm = new ListSingleSelectionModel();
  8455.       sm.addEventListener('change', this.cookieSelectionChange_.bind(this));
  8456.       sm.addEventListener('leadIndexChange', this.cookieLeadChange_.bind(this));
  8457.       this.selectionModel = sm;
  8458.       this.infoNodes = {};
  8459.       this.fixedHeight = false;
  8460.       var doc = this.ownerDocument;
  8461.       // Create a table for each type of site data (e.g. cookies, databases,
  8462.       // etc.) and save it so that we can reuse it for all origins.
  8463.       for (var type in cookieInfo) {
  8464.         var table = doc.createElement('table');
  8465.         table.className = 'cookie-details-table';
  8466.         var tbody = doc.createElement('tbody');
  8467.         table.appendChild(tbody);
  8468.         var info = {};
  8469.         for (var i = 0; i < cookieInfo[type].length; i++) {
  8470.           var tr = doc.createElement('tr');
  8471.           var name = doc.createElement('td');
  8472.           var data = doc.createElement('td');
  8473.           var pair = cookieInfo[type][i];
  8474.           name.className = 'cookie-details-label';
  8475.           name.textContent = loadTimeData.getString(pair[1]);
  8476.           data.className = 'cookie-details-value';
  8477.           data.textContent = '';
  8478.           tr.appendChild(name);
  8479.           tr.appendChild(data);
  8480.           tbody.appendChild(tr);
  8481.           info[pair[0]] = data;
  8482.         }
  8483.         this.infoNodes[type] = {table: table, info: info};
  8484.       }
  8485.     },
  8486.  
  8487.     /**
  8488.      * Handles key down events and looks for left and right arrows, then
  8489.      * dispatches to the currently expanded item, if any.
  8490.      * @param {Event} e The keydown event.
  8491.      * @private
  8492.      */
  8493.     handleKeyLeftRight_: function(e) {
  8494.       var id = e.keyIdentifier;
  8495.       if ((id == 'Left' || id == 'Right') && this.expandedItem) {
  8496.         var cs = this.ownerDocument.defaultView.getComputedStyle(this);
  8497.         var rtl = cs.direction == 'rtl';
  8498.         if ((!rtl && id == 'Left') || (rtl && id == 'Right'))
  8499.           this.expandedItem.selectedIndex--;
  8500.         else
  8501.           this.expandedItem.selectedIndex++;
  8502.         this.scrollIndexIntoView(this.expandedItem.listIndex);
  8503.         // Prevent the page itself from scrolling.
  8504.         e.preventDefault();
  8505.       }
  8506.     },
  8507.  
  8508.     /**
  8509.      * Called on selection model selection changes.
  8510.      * @param {Event} ce The selection change event.
  8511.      * @private
  8512.      */
  8513.     cookieSelectionChange_: function(ce) {
  8514.       ce.changes.forEach(function(change) {
  8515.           var listItem = this.getListItemByIndex(change.index);
  8516.           if (listItem) {
  8517.             if (!change.selected) {
  8518.               // We set a timeout here, rather than setting the item unexpanded
  8519.               // immediately, so that if another item gets set expanded right
  8520.               // away, it will be expanded before this item is unexpanded. It
  8521.               // will notice that, and unexpand this item in sync with its own
  8522.               // expansion. Later, this callback will end up having no effect.
  8523.               window.setTimeout(function() {
  8524.                 if (!listItem.selected || !listItem.lead)
  8525.                   listItem.expanded = false;
  8526.               }, 0);
  8527.             } else if (listItem.lead) {
  8528.               listItem.expanded = true;
  8529.             }
  8530.           }
  8531.         }, this);
  8532.     },
  8533.  
  8534.     /**
  8535.      * Called on selection model lead changes.
  8536.      * @param {Event} pe The lead change event.
  8537.      * @private
  8538.      */
  8539.     cookieLeadChange_: function(pe) {
  8540.       if (pe.oldValue != -1) {
  8541.         var listItem = this.getListItemByIndex(pe.oldValue);
  8542.         if (listItem) {
  8543.           // See cookieSelectionChange_ above for why we use a timeout here.
  8544.           window.setTimeout(function() {
  8545.             if (!listItem.lead || !listItem.selected)
  8546.               listItem.expanded = false;
  8547.           }, 0);
  8548.         }
  8549.       }
  8550.       if (pe.newValue != -1) {
  8551.         var listItem = this.getListItemByIndex(pe.newValue);
  8552.         if (listItem && listItem.selected)
  8553.           listItem.expanded = true;
  8554.       }
  8555.     },
  8556.  
  8557.     /**
  8558.      * The currently expanded item. Used by CookieListItem above.
  8559.      * @type {?CookieListItem}
  8560.      */
  8561.     expandedItem: null,
  8562.  
  8563.     // from cr.ui.List
  8564.     /** @override */
  8565.     createItem: function(data) {
  8566.       // We use the cached expanded item in order to allow it to maintain some
  8567.       // state (like its fixed height, and which bubble is selected).
  8568.       if (this.expandedItem && this.expandedItem.origin == data)
  8569.         return this.expandedItem;
  8570.       return new CookieListItem(data, this);
  8571.     },
  8572.  
  8573.     // from options.DeletableItemList
  8574.     /** @override */
  8575.     deleteItemAtIndex: function(index) {
  8576.       var item = this.dataModel.item(index);
  8577.       if (item) {
  8578.         var pathId = item.pathId;
  8579.         if (pathId)
  8580.           chrome.send('removeCookie', [pathId]);
  8581.       }
  8582.     },
  8583.  
  8584.     /**
  8585.      * Insert the given list of cookie tree nodes at the given index.
  8586.      * Both CookiesList and CookieTreeNode implement this API.
  8587.      * @param {Array.<Object>} data The data objects for the nodes to add.
  8588.      * @param {number} start The index at which to start inserting the nodes.
  8589.      */
  8590.     insertAt: function(data, start) {
  8591.       spliceTreeNodes(data, start, this.dataModel);
  8592.     },
  8593.  
  8594.     /**
  8595.      * Remove a cookie tree node from the given index.
  8596.      * Both CookiesList and CookieTreeNode implement this API.
  8597.      * @param {number} index The index of the tree node to remove.
  8598.      */
  8599.     remove: function(index) {
  8600.       if (index < this.dataModel.length)
  8601.         this.dataModel.splice(index, 1);
  8602.     },
  8603.  
  8604.     /**
  8605.      * Clears the list.
  8606.      * Both CookiesList and CookieTreeNode implement this API.
  8607.      * It is used by CookiesList.loadChildren().
  8608.      */
  8609.     clear: function() {
  8610.       parentLookup = {};
  8611.       this.dataModel.splice(0, this.dataModel.length);
  8612.       this.redraw();
  8613.     },
  8614.  
  8615.     /**
  8616.      * Add tree nodes by given parent.
  8617.      * @param {Object} parent The parent node.
  8618.      * @param {number} start The index at which to start inserting the nodes.
  8619.      * @param {Array} nodesData Nodes data array.
  8620.      * @private
  8621.      */
  8622.     addByParent_: function(parent, start, nodesData) {
  8623.       if (!parent)
  8624.         return;
  8625.  
  8626.       parent.startBatchUpdates();
  8627.       parent.insertAt(nodesData, start);
  8628.       parent.endBatchUpdates();
  8629.  
  8630.       cr.dispatchSimpleEvent(this, 'change');
  8631.     },
  8632.  
  8633.     /**
  8634.      * Add tree nodes by parent id.
  8635.      * This is used by cookies_view.js.
  8636.      * @param {string} parentId Id of the parent node.
  8637.      * @param {number} start The index at which to start inserting the nodes.
  8638.      * @param {Array} nodesData Nodes data array.
  8639.      */
  8640.     addByParentId: function(parentId, start, nodesData) {
  8641.       var parent = parentId ? parentLookup[parentId] : this;
  8642.       this.addByParent_(parent, start, nodesData);
  8643.     },
  8644.  
  8645.     /**
  8646.      * Removes tree nodes by parent id.
  8647.      * This is used by cookies_view.js.
  8648.      * @param {string} parentId Id of the parent node.
  8649.      * @param {number} start The index at which to start removing the nodes.
  8650.      * @param {number} count Number of nodes to remove.
  8651.      */
  8652.     removeByParentId: function(parentId, start, count) {
  8653.       var parent = parentId ? parentLookup[parentId] : this;
  8654.       if (!parent)
  8655.         return;
  8656.  
  8657.       parent.startBatchUpdates();
  8658.       while (count-- > 0)
  8659.         parent.remove(start);
  8660.       parent.endBatchUpdates();
  8661.  
  8662.       cr.dispatchSimpleEvent(this, 'change');
  8663.     },
  8664.  
  8665.     /**
  8666.      * Loads the immediate children of given parent node.
  8667.      * This is used by cookies_view.js.
  8668.      * @param {string} parentId Id of the parent node.
  8669.      * @param {Array} children The immediate children of parent node.
  8670.      */
  8671.     loadChildren: function(parentId, children) {
  8672.       if (parentId)
  8673.         delete lookupRequests[parentId];
  8674.       var parent = parentId ? parentLookup[parentId] : this;
  8675.       if (!parent)
  8676.         return;
  8677.  
  8678.       parent.startBatchUpdates();
  8679.       parent.clear();
  8680.       this.addByParent_(parent, 0, children);
  8681.       parent.endBatchUpdates();
  8682.     },
  8683.   };
  8684.  
  8685.   return {
  8686.     CookiesList: CookiesList
  8687.   };
  8688. });
  8689.  
  8690. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8691. // Use of this source code is governed by a BSD-style license that can be
  8692. // found in the LICENSE file.
  8693.  
  8694. cr.define('options', function() {
  8695.  
  8696.   var OptionsPage = options.OptionsPage;
  8697.  
  8698.   /////////////////////////////////////////////////////////////////////////////
  8699.   // CookiesView class:
  8700.  
  8701.   /**
  8702.    * Encapsulated handling of the cookies and other site data page.
  8703.    * @constructor
  8704.    */
  8705.   function CookiesView(model) {
  8706.     OptionsPage.call(this, 'cookies',
  8707.                      loadTimeData.getString('cookiesViewPageTabTitle'),
  8708.                      'cookies-view-page');
  8709.   }
  8710.  
  8711.   cr.addSingletonGetter(CookiesView);
  8712.  
  8713.   CookiesView.prototype = {
  8714.     __proto__: OptionsPage.prototype,
  8715.  
  8716.     /**
  8717.      * The timer id of the timer set on search query change events.
  8718.      * @type {number}
  8719.      * @private
  8720.      */
  8721.     queryDelayTimerId_: 0,
  8722.  
  8723.     /**
  8724.      * The most recent search query, empty string if the query is empty.
  8725.      * @type {string}
  8726.      * @private
  8727.      */
  8728.     lastQuery_: '',
  8729.  
  8730.     initializePage: function() {
  8731.       OptionsPage.prototype.initializePage.call(this);
  8732.  
  8733.       this.pageDiv.querySelector('.cookies-search-box').addEventListener(
  8734.           'search', this.handleSearchQueryChange_.bind(this));
  8735.  
  8736.       this.pageDiv.querySelector('.remove-all-cookies-button').onclick =
  8737.           function(e) {
  8738.             chrome.send('removeAllCookies');
  8739.           };
  8740.  
  8741.       var cookiesList = this.pageDiv.querySelector('.cookies-list');
  8742.       options.CookiesList.decorate(cookiesList);
  8743.  
  8744.       this.addEventListener('visibleChange', this.handleVisibleChange_);
  8745.  
  8746.       this.pageDiv.querySelector('.cookies-view-overlay-confirm').onclick =
  8747.           OptionsPage.closeOverlay.bind(OptionsPage);
  8748.     },
  8749.  
  8750.     /** @override */
  8751.     didShowPage: function() {
  8752.       this.pageDiv.querySelector('.cookies-search-box').value = '';
  8753.     },
  8754.  
  8755.     /**
  8756.      * Search cookie using text in |cookies-search-box|.
  8757.      */
  8758.     searchCookie: function() {
  8759.       this.queryDelayTimerId_ = 0;
  8760.       var filter = this.pageDiv.querySelector('.cookies-search-box').value;
  8761.       if (this.lastQuery_ != filter) {
  8762.         this.lastQuery_ = filter;
  8763.         chrome.send('updateCookieSearchResults', [filter]);
  8764.       }
  8765.     },
  8766.  
  8767.     /**
  8768.      * Handles search query changes.
  8769.      * @param {!Event} e The event object.
  8770.      * @private
  8771.      */
  8772.     handleSearchQueryChange_: function(e) {
  8773.       if (this.queryDelayTimerId_)
  8774.         window.clearTimeout(this.queryDelayTimerId_);
  8775.  
  8776.       this.queryDelayTimerId_ = window.setTimeout(
  8777.           this.searchCookie.bind(this), 500);
  8778.     },
  8779.  
  8780.     initialized_: false,
  8781.  
  8782.     /**
  8783.      * Handler for OptionsPage's visible property change event.
  8784.      * @param {Event} e Property change event.
  8785.      * @private
  8786.      */
  8787.     handleVisibleChange_: function(e) {
  8788.       if (!this.visible)
  8789.         return;
  8790.  
  8791.       chrome.send('reloadCookies');
  8792.  
  8793.       if (!this.initialized_) {
  8794.         this.initialized_ = true;
  8795.         this.searchCookie();
  8796.       } else {
  8797.         this.pageDiv.querySelector('.cookies-list').redraw();
  8798.       }
  8799.  
  8800.       this.pageDiv.querySelector('.cookies-search-box').focus();
  8801.     },
  8802.   };
  8803.  
  8804.   // CookiesViewHandler callbacks.
  8805.   CookiesView.onTreeItemAdded = function(args) {
  8806.     $('cookies-list').addByParentId(args[0], args[1], args[2]);
  8807.   };
  8808.  
  8809.   CookiesView.onTreeItemRemoved = function(args) {
  8810.     $('cookies-list').removeByParentId(args[0], args[1], args[2]);
  8811.   };
  8812.  
  8813.   CookiesView.loadChildren = function(args) {
  8814.     $('cookies-list').loadChildren(args[0], args[1]);
  8815.   };
  8816.  
  8817.   // Export
  8818.   return {
  8819.     CookiesView: CookiesView
  8820.   };
  8821.  
  8822. });
  8823.  
  8824. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8825. // Use of this source code is governed by a BSD-style license that can be
  8826. // found in the LICENSE file.
  8827.  
  8828. cr.define('options', function() {
  8829.   var OptionsPage = options.OptionsPage;
  8830.  
  8831.   /**
  8832.    * FactoryResetOverlay class
  8833.    * Encapsulated handling of the Factory Reset confirmation overlay page.
  8834.    * @class
  8835.    */
  8836.   function FactoryResetOverlay() {
  8837.     OptionsPage.call(this, 'factoryResetData',
  8838.                      loadTimeData.getString('factoryResetTitle'),
  8839.                      'factory-reset-overlay');
  8840.   }
  8841.  
  8842.   cr.addSingletonGetter(FactoryResetOverlay);
  8843.  
  8844.   FactoryResetOverlay.prototype = {
  8845.     // Inherit FactoryResetOverlay from OptionsPage.
  8846.     __proto__: OptionsPage.prototype,
  8847.  
  8848.     /**
  8849.      * Initialize the page.
  8850.      */
  8851.     initializePage: function() {
  8852.       // Call base class implementation to starts preference initialization.
  8853.       OptionsPage.prototype.initializePage.call(this);
  8854.  
  8855.       $('factory-reset-data-dismiss').onclick = function(event) {
  8856.         FactoryResetOverlay.dismiss();
  8857.       };
  8858.       $('factory-reset-data-restart').onclick = function(event) {
  8859.         chrome.send('performFactoryResetRestart');
  8860.       };
  8861.     },
  8862.   };
  8863.  
  8864.   FactoryResetOverlay.dismiss = function() {
  8865.     OptionsPage.closeOverlay();
  8866.   };
  8867.  
  8868.   // Export
  8869.   return {
  8870.     FactoryResetOverlay: FactoryResetOverlay
  8871.   };
  8872. });
  8873.  
  8874. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8875. // Use of this source code is governed by a BSD-style license that can be
  8876. // found in the LICENSE file.
  8877.  
  8878. if (loadTimeData.getBoolean('managedUsersEnabled')) {
  8879.  
  8880. cr.define('options', function() {
  8881.   /** @const */ var OptionsPage = options.OptionsPage;
  8882.  
  8883.   //////////////////////////////////////////////////////////////////////////////
  8884.   // ManagedUserSettings class:
  8885.  
  8886.   /**
  8887.    * Encapsulated handling of the Managed User Settings page.
  8888.    * @constructor
  8889.    * @class
  8890.    */
  8891.   function ManagedUserSettings() {
  8892.     OptionsPage.call(
  8893.         this,
  8894.         'manageduser',
  8895.         loadTimeData.getString('managedUserSettingsPageTabTitle'),
  8896.         'managed-user-settings-page');
  8897.   }
  8898.  
  8899.   cr.addSingletonGetter(ManagedUserSettings);
  8900.  
  8901.   ManagedUserSettings.prototype = {
  8902.     // Inherit from OptionsPage.
  8903.     __proto__: OptionsPage.prototype,
  8904.  
  8905.     /**
  8906.      * Initialize the page.
  8907.      * @override
  8908.      */
  8909.     initializePage: function() {
  8910.       // Call base class implementation to start preference initialization.
  8911.       OptionsPage.prototype.initializePage.call(this);
  8912.  
  8913.       $('get-content-packs-button').onclick = function(event) {
  8914.         window.open(loadTimeData.getString('getContentPacksURL'));
  8915.       };
  8916.  
  8917.       $('managed-user-settings-confirm').onclick = function() {
  8918.         OptionsPage.closeOverlay();
  8919.       };
  8920.  
  8921.       $('set-passphrase').onclick = function() {
  8922.         // TODO(bauerb): Set passphrase
  8923.       };
  8924.     },
  8925.   };
  8926.  
  8927.    // Export
  8928.   return {
  8929.     ManagedUserSettings: ManagedUserSettings
  8930.   };
  8931. });
  8932.  
  8933. }
  8934.  
  8935. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  8936. // Use of this source code is governed by a BSD-style license that can be
  8937. // found in the LICENSE file.
  8938.  
  8939. cr.define('options', function() {
  8940.  
  8941.   var OptionsPage = options.OptionsPage;
  8942.  
  8943.   /**
  8944.    * FontSettings class
  8945.    * Encapsulated handling of the 'Fonts and Encoding' page.
  8946.    * @class
  8947.    */
  8948.   function FontSettings() {
  8949.     OptionsPage.call(this,
  8950.                      'fonts',
  8951.                      loadTimeData.getString('fontSettingsPageTabTitle'),
  8952.                      'font-settings');
  8953.   }
  8954.  
  8955.   cr.addSingletonGetter(FontSettings);
  8956.  
  8957.   FontSettings.prototype = {
  8958.     __proto__: OptionsPage.prototype,
  8959.  
  8960.     /**
  8961.      * Initialize the page.
  8962.      */
  8963.     initializePage: function() {
  8964.       OptionsPage.prototype.initializePage.call(this);
  8965.  
  8966.       var standardFontRange = $('standard-font-size');
  8967.       standardFontRange.valueMap = [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20,
  8968.           22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 56, 64, 72];
  8969.       standardFontRange.addEventListener(
  8970.           'change', this.standardRangeChanged_.bind(this, standardFontRange));
  8971.       standardFontRange.customChangeHandler =
  8972.           this.standardFontSizeChanged_.bind(standardFontRange);
  8973.  
  8974.       var minimumFontRange = $('minimum-font-size');
  8975.       minimumFontRange.valueMap = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
  8976.           18, 20, 22, 24];
  8977.       minimumFontRange.addEventListener(
  8978.           'change', this.minimumRangeChanged_.bind(this, minimumFontRange));
  8979.       minimumFontRange.customChangeHandler =
  8980.           this.minimumFontSizeChanged_.bind(minimumFontRange);
  8981.  
  8982.       var placeholder = loadTimeData.getString('fontSettingsPlaceholder');
  8983.       var elements = [$('standard-font-family'), $('serif-font-family'),
  8984.                       $('sans-serif-font-family'), $('fixed-font-family'),
  8985.                       $('font-encoding')];
  8986.       elements.forEach(function(el) {
  8987.         el.appendChild(new Option(placeholder));
  8988.         el.setDisabled('noFontsAvailable', true);
  8989.       });
  8990.  
  8991.       $('font-settings-confirm').onclick = function() {
  8992.         OptionsPage.closeOverlay();
  8993.       };
  8994.     },
  8995.  
  8996.     /**
  8997.      * Called by the options page when this page has been shown.
  8998.      */
  8999.     didShowPage: function() {
  9000.       // The fonts list may be large so we only load it when this page is
  9001.       // loaded for the first time.  This makes opening the options window
  9002.       // faster and consume less memory if the user never opens the fonts
  9003.       // dialog.
  9004.       if (!this.hasShown) {
  9005.         chrome.send('fetchFontsData');
  9006.         this.hasShown = true;
  9007.       }
  9008.     },
  9009.  
  9010.     /**
  9011.      * Handler that is called when the user changes the position of the standard
  9012.      * font size slider. This allows the UI to show a preview of the change
  9013.      * before the slider has been released and the associated prefs updated.
  9014.      * @param {Element} el The slider input element.
  9015.      * @param {Event} event Change event.
  9016.      * @private
  9017.      */
  9018.     standardRangeChanged_: function(el, event) {
  9019.       var size = el.mapPositionToPref(el.value);
  9020.       var fontSampleEl = $('standard-font-sample');
  9021.       this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
  9022.                             true);
  9023.  
  9024.       fontSampleEl = $('serif-font-sample');
  9025.       this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
  9026.                             true);
  9027.  
  9028.       fontSampleEl = $('sans-serif-font-sample');
  9029.       this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
  9030.                             true);
  9031.  
  9032.       fontSampleEl = $('fixed-font-sample');
  9033.       this.setUpFontSample_(fontSampleEl,
  9034.                             size - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD,
  9035.                             fontSampleEl.style.fontFamily, false);
  9036.     },
  9037.  
  9038.     /**
  9039.      * Sets the 'default_fixed_font_size' preference when the user changes the
  9040.      * standard font size.
  9041.      * @param {Event} event Change event.
  9042.      * @private
  9043.      */
  9044.     standardFontSizeChanged_: function(event) {
  9045.       var size = this.mapPositionToPref(this.value);
  9046.       Preferences.setIntegerPref(
  9047.         'webkit.webprefs.default_fixed_font_size',
  9048.         size - OptionsPage.SIZE_DIFFERENCE_FIXED_STANDARD, true);
  9049.       return false;
  9050.     },
  9051.  
  9052.     /**
  9053.      * Handler that is called when the user changes the position of the minimum
  9054.      * font size slider. This allows the UI to show a preview of the change
  9055.      * before the slider has been released and the associated prefs updated.
  9056.      * @param {Element} el The slider input element.
  9057.      * @param {Event} event Change event.
  9058.      * @private
  9059.      */
  9060.     minimumRangeChanged_: function(el, event) {
  9061.       var size = el.mapPositionToPref(el.value);
  9062.       var fontSampleEl = $('minimum-font-sample');
  9063.       this.setUpFontSample_(fontSampleEl, size, fontSampleEl.style.fontFamily,
  9064.                             true);
  9065.     },
  9066.  
  9067.     /**
  9068.      * Sets the 'minimum_logical_font_size' preference when the user changes the
  9069.      * minimum font size.
  9070.      * @param {Event} event Change event.
  9071.      * @private
  9072.      */
  9073.     minimumFontSizeChanged_: function(event) {
  9074.       var size = this.mapPositionToPref(this.value);
  9075.       Preferences.setIntegerPref(
  9076.         'webkit.webprefs.minimum_logical_font_size', size, true);
  9077.       return false;
  9078.     },
  9079.  
  9080.     /**
  9081.      * Sets the text, font size and font family of the sample text.
  9082.      * @param {Element} el The div containing the sample text.
  9083.      * @param {number} size The font size of the sample text.
  9084.      * @param {string} font The font family of the sample text.
  9085.      * @param {bool} showSize True if the font size should appear in the sample.
  9086.      * @private
  9087.      */
  9088.     setUpFontSample_: function(el, size, font, showSize) {
  9089.       var prefix = showSize ? (size + ': ') : '';
  9090.       el.textContent = prefix +
  9091.           loadTimeData.getString('fontSettingsLoremIpsum');
  9092.       el.style.fontSize = size + 'px';
  9093.       if (font)
  9094.         el.style.fontFamily = font;
  9095.     },
  9096.  
  9097.     /**
  9098.      * Populates a select list and selects the specified item.
  9099.      * @param {Element} element The select element to populate.
  9100.      * @param {Array} items The array of items from which to populate.
  9101.      * @param {string} selectedValue The selected item.
  9102.      * @private
  9103.      */
  9104.     populateSelect_: function(element, items, selectedValue) {
  9105.       // Remove any existing content.
  9106.       element.textContent = '';
  9107.  
  9108.       // Insert new child nodes into select element.
  9109.       var value, text, selected, option;
  9110.       for (var i = 0; i < items.length; i++) {
  9111.         value = items[i][0];
  9112.         text = items[i][1];
  9113.         dir = items[i][2];
  9114.         if (text) {
  9115.           selected = value == selectedValue;
  9116.           var option = new Option(text, value, false, selected);
  9117.           option.dir = dir;
  9118.           element.appendChild(option);
  9119.         } else {
  9120.           element.appendChild(document.createElement('hr'));
  9121.         }
  9122.       }
  9123.  
  9124.       element.setDisabled('noFontsAvailable', false);
  9125.     }
  9126.   };
  9127.  
  9128.   // Chrome callbacks
  9129.   FontSettings.setFontsData = function(fonts, encodings, selectedValues) {
  9130.     FontSettings.getInstance().populateSelect_($('standard-font-family'), fonts,
  9131.                                                selectedValues[0]);
  9132.     FontSettings.getInstance().populateSelect_($('serif-font-family'), fonts,
  9133.                                                selectedValues[1]);
  9134.     FontSettings.getInstance().populateSelect_($('sans-serif-font-family'),
  9135.                                                fonts, selectedValues[2]);
  9136.     FontSettings.getInstance().populateSelect_($('fixed-font-family'), fonts,
  9137.                                                selectedValues[3]);
  9138.     FontSettings.getInstance().populateSelect_($('font-encoding'), encodings,
  9139.                                                selectedValues[4]);
  9140.   };
  9141.  
  9142.   FontSettings.setUpStandardFontSample = function(font, size) {
  9143.     FontSettings.getInstance().setUpFontSample_($('standard-font-sample'), size,
  9144.                                                 font, true);
  9145.   };
  9146.  
  9147.   FontSettings.setUpSerifFontSample = function(font, size) {
  9148.     FontSettings.getInstance().setUpFontSample_($('serif-font-sample'), size,
  9149.                                                 font, true);
  9150.   };
  9151.  
  9152.   FontSettings.setUpSansSerifFontSample = function(font, size) {
  9153.     FontSettings.getInstance().setUpFontSample_($('sans-serif-font-sample'),
  9154.                                                 size, font, true);
  9155.   };
  9156.  
  9157.   FontSettings.setUpFixedFontSample = function(font, size) {
  9158.     FontSettings.getInstance().setUpFontSample_($('fixed-font-sample'),
  9159.                                                 size, font, false);
  9160.   };
  9161.  
  9162.   FontSettings.setUpMinimumFontSample = function(size) {
  9163.     // If size is less than 6, represent it as six in the sample to account
  9164.     // for the minimum logical font size.
  9165.     if (size < 6)
  9166.       size = 6;
  9167.     FontSettings.getInstance().setUpFontSample_($('minimum-font-sample'), size,
  9168.                                                 null, true);
  9169.   };
  9170.  
  9171.   // Export
  9172.   return {
  9173.     FontSettings: FontSettings
  9174.   };
  9175. });
  9176.  
  9177.  
  9178. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9179. // Use of this source code is governed by a BSD-style license that can be
  9180. // found in the LICENSE file.
  9181.  
  9182. cr.define('options', function() {
  9183.   /** @const */ var OptionsPage = options.OptionsPage;
  9184.  
  9185.   /////////////////////////////////////////////////////////////////////////////
  9186.   // HandlerOptions class:
  9187.  
  9188.   /**
  9189.    * Encapsulated handling of handler options page.
  9190.    * @constructor
  9191.    */
  9192.   function HandlerOptions() {
  9193.     this.activeNavTab = null;
  9194.     OptionsPage.call(this,
  9195.                      'handlers',
  9196.                      loadTimeData.getString('handlersPageTabTitle'),
  9197.                      'handler-options');
  9198.   }
  9199.  
  9200.   cr.addSingletonGetter(HandlerOptions);
  9201.  
  9202.   HandlerOptions.prototype = {
  9203.     __proto__: OptionsPage.prototype,
  9204.  
  9205.     /**
  9206.      * The handlers list.
  9207.      * @type {DeletableItemList}
  9208.      * @private
  9209.      */
  9210.     handlersList_: null,
  9211.  
  9212.     /** @override */
  9213.     initializePage: function() {
  9214.       OptionsPage.prototype.initializePage.call(this);
  9215.  
  9216.       this.createHandlersList_();
  9217.  
  9218.       $('handler-options-overlay-confirm').onclick =
  9219.           OptionsPage.closeOverlay.bind(OptionsPage);
  9220.     },
  9221.  
  9222.     /**
  9223.      * Creates, decorates and initializes the handlers list.
  9224.      * @private
  9225.      */
  9226.     createHandlersList_: function() {
  9227.       this.handlersList_ = $('handlers-list');
  9228.       options.HandlersList.decorate(this.handlersList_);
  9229.       this.handlersList_.autoExpands = true;
  9230.  
  9231.       this.ignoredHandlersList_ = $('ignored-handlers-list');
  9232.       options.IgnoredHandlersList.decorate(this.ignoredHandlersList_);
  9233.       this.ignoredHandlersList_.autoExpands = true;
  9234.     },
  9235.   };
  9236.  
  9237.   /**
  9238.    * Sets the list of handlers shown by the view.
  9239.    * @param {Array} Handlers to be shown in the view.
  9240.    */
  9241.   HandlerOptions.setHandlers = function(handlers) {
  9242.     $('handlers-list').setHandlers(handlers);
  9243.   };
  9244.  
  9245.   /**
  9246.    * Sets the list of ignored handlers shown by the view.
  9247.    * @param {Array} Handlers to be shown in the view.
  9248.    */
  9249.   HandlerOptions.setIgnoredHandlers = function(handlers) {
  9250.     $('ignored-handlers-section').hidden = handlers.length == 0;
  9251.     $('ignored-handlers-list').setHandlers(handlers);
  9252.   };
  9253.  
  9254.   return {
  9255.     HandlerOptions: HandlerOptions
  9256.   };
  9257. });
  9258.  
  9259. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9260. // Use of this source code is governed by a BSD-style license that can be
  9261. // found in the LICENSE file.
  9262.  
  9263. cr.define('options', function() {
  9264.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  9265.   /** @const */ var List = cr.ui.List;
  9266.   /** @const */ var ListItem = cr.ui.ListItem;
  9267.   /** @const */ var HandlerOptions = options.HandlerOptions;
  9268.   /** @const */ var DeletableItem = options.DeletableItem;
  9269.   /** @const */ var DeletableItemList = options.DeletableItemList;
  9270.  
  9271.   /**
  9272.    * Creates a new ignored protocol / content handler list item.
  9273.    *
  9274.    * Accepts values in the form
  9275.    *   ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
  9276.    * @param {Object} entry A dictionary describing the handlers for a given
  9277.    *     protocol.
  9278.    * @constructor
  9279.    * @extends {cr.ui.DeletableItemList}
  9280.    */
  9281.   function IgnoredHandlersListItem(entry) {
  9282.     var el = cr.doc.createElement('div');
  9283.     el.dataItem = entry;
  9284.     el.__proto__ = IgnoredHandlersListItem.prototype;
  9285.     el.decorate();
  9286.     return el;
  9287.   }
  9288.  
  9289.   IgnoredHandlersListItem.prototype = {
  9290.     __proto__: DeletableItem.prototype,
  9291.  
  9292.     /** @override */
  9293.     decorate: function() {
  9294.       DeletableItem.prototype.decorate.call(this);
  9295.  
  9296.       // Protocol.
  9297.       var protocolElement = document.createElement('div');
  9298.       protocolElement.textContent = this.dataItem[0];
  9299.       protocolElement.className = 'handlers-type-column';
  9300.       this.contentElement_.appendChild(protocolElement);
  9301.  
  9302.       // Site title.
  9303.       var titleElement = document.createElement('div');
  9304.       titleElement.textContent = this.dataItem[2];
  9305.       titleElement.className = 'handlers-site-column';
  9306.       titleElement.title = this.dataItem[1];
  9307.       this.contentElement_.appendChild(titleElement);
  9308.     },
  9309.   };
  9310.  
  9311.  
  9312.   var IgnoredHandlersList = cr.ui.define('list');
  9313.  
  9314.   IgnoredHandlersList.prototype = {
  9315.     __proto__: DeletableItemList.prototype,
  9316.  
  9317.     createItem: function(entry) {
  9318.       return new IgnoredHandlersListItem(entry);
  9319.     },
  9320.  
  9321.     deleteItemAtIndex: function(index) {
  9322.       chrome.send('removeIgnoredHandler', [this.dataModel.item(index)]);
  9323.     },
  9324.  
  9325.     /**
  9326.      * The length of the list.
  9327.      */
  9328.     get length() {
  9329.       return this.dataModel.length;
  9330.     },
  9331.  
  9332.     /**
  9333.      * Set the protocol handlers displayed by this list.  See
  9334.      * IgnoredHandlersListItem for an example of the format the list should
  9335.      * take.
  9336.      *
  9337.      * @param {Object} list A list of ignored protocol handlers.
  9338.      */
  9339.     setHandlers: function(list) {
  9340.       this.dataModel = new ArrayDataModel(list);
  9341.     },
  9342.   };
  9343.  
  9344.  
  9345.  
  9346.   /**
  9347.    * Creates a new protocol / content handler list item.
  9348.    *
  9349.    * Accepts values in the form
  9350.    * { protocol: 'mailto',
  9351.    *   handlers: [
  9352.    *     ['mailto', 'http://www.thesite.com/%s', 'The title of the protocol'],
  9353.    *     ...,
  9354.    *   ],
  9355.    * }
  9356.    * @param {Object} entry A dictionary describing the handlers for a given
  9357.    *     protocol.
  9358.    * @constructor
  9359.    * @extends {cr.ui.ListItem}
  9360.    */
  9361.   function HandlerListItem(entry) {
  9362.     var el = cr.doc.createElement('div');
  9363.     el.dataItem = entry;
  9364.     el.__proto__ = HandlerListItem.prototype;
  9365.     el.decorate();
  9366.     return el;
  9367.   }
  9368.  
  9369.   HandlerListItem.prototype = {
  9370.     __proto__: ListItem.prototype,
  9371.  
  9372.     buildWidget_: function(data, delegate) {
  9373.       // Protocol.
  9374.       var protocolElement = document.createElement('div');
  9375.       protocolElement.textContent = data.protocol;
  9376.       protocolElement.className = 'handlers-type-column';
  9377.       this.appendChild(protocolElement);
  9378.  
  9379.       // Handler selection.
  9380.       var handlerElement = document.createElement('div');
  9381.       var selectElement = document.createElement('select');
  9382.       var defaultOptionElement = document.createElement('option');
  9383.       defaultOptionElement.selected = data.default_handler == -1;
  9384.       defaultOptionElement.textContent =
  9385.           loadTimeData.getString('handlers_none_handler');
  9386.       defaultOptionElement.value = -1;
  9387.       selectElement.appendChild(defaultOptionElement);
  9388.  
  9389.       for (var i = 0; i < data.handlers.length; ++i) {
  9390.         var optionElement = document.createElement('option');
  9391.         optionElement.selected = i == data.default_handler;
  9392.         optionElement.textContent = data.handlers[i][2];
  9393.         optionElement.value = i;
  9394.         selectElement.appendChild(optionElement);
  9395.       }
  9396.  
  9397.       selectElement.addEventListener('change', function(e) {
  9398.         var index = e.target.value;
  9399.         if (index == -1) {
  9400.           this.classList.add('none');
  9401.           delegate.clearDefault(data.protocol);
  9402.         } else {
  9403.           handlerElement.classList.remove('none');
  9404.           delegate.setDefault(data.handlers[index]);
  9405.         }
  9406.       });
  9407.       handlerElement.appendChild(selectElement);
  9408.       handlerElement.className = 'handlers-site-column';
  9409.       if (data.default_handler == -1)
  9410.         this.classList.add('none');
  9411.       this.appendChild(handlerElement);
  9412.  
  9413.       // Remove link.
  9414.       var removeElement = document.createElement('div');
  9415.       removeElement.textContent =
  9416.           loadTimeData.getString('handlers_remove_link');
  9417.       removeElement.addEventListener('click', function(e) {
  9418.         var value = selectElement ? selectElement.value : 0;
  9419.         delegate.removeHandler(value, data.handlers[value]);
  9420.       });
  9421.       removeElement.className = 'handlers-remove-column handlers-remove-link';
  9422.       this.appendChild(removeElement);
  9423.     },
  9424.  
  9425.     /** @override */
  9426.     decorate: function() {
  9427.       ListItem.prototype.decorate.call(this);
  9428.  
  9429.       var self = this;
  9430.       var delegate = {
  9431.         removeHandler: function(index, handler) {
  9432.           chrome.send('removeHandler', [handler]);
  9433.         },
  9434.         setDefault: function(handler) {
  9435.           chrome.send('setDefault', [handler]);
  9436.         },
  9437.         clearDefault: function(protocol) {
  9438.           chrome.send('clearDefault', [protocol]);
  9439.         },
  9440.       };
  9441.  
  9442.       this.buildWidget_(this.dataItem, delegate);
  9443.     },
  9444.   };
  9445.  
  9446.   /**
  9447.    * Create a new passwords list.
  9448.    * @constructor
  9449.    * @extends {cr.ui.List}
  9450.    */
  9451.   var HandlersList = cr.ui.define('list');
  9452.  
  9453.   HandlersList.prototype = {
  9454.     __proto__: List.prototype,
  9455.  
  9456.     /** @override */
  9457.     createItem: function(entry) {
  9458.       return new HandlerListItem(entry);
  9459.     },
  9460.  
  9461.     /**
  9462.      * The length of the list.
  9463.      */
  9464.     get length() {
  9465.       return this.dataModel.length;
  9466.     },
  9467.  
  9468.     /**
  9469.      * Set the protocol handlers displayed by this list.
  9470.      * See HandlerListItem for an example of the format the list should take.
  9471.      *
  9472.      * @param {Object} list A list of protocols with their registered handlers.
  9473.      */
  9474.     setHandlers: function(list) {
  9475.       this.dataModel = new ArrayDataModel(list);
  9476.     },
  9477.   };
  9478.  
  9479.   return {
  9480.     IgnoredHandlersListItem: IgnoredHandlersListItem,
  9481.     IgnoredHandlersList: IgnoredHandlersList,
  9482.     HandlerListItem: HandlerListItem,
  9483.     HandlersList: HandlersList,
  9484.   };
  9485. });
  9486.  
  9487. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9488. // Use of this source code is governed by a BSD-style license that can be
  9489. // found in the LICENSE file.
  9490.  
  9491. cr.define('options', function() {
  9492.   /** @const */ var OptionsPage = options.OptionsPage;
  9493.   /** @const */ var SettingsDialog = options.SettingsDialog;
  9494.  
  9495.   /**
  9496.    * HomePageOverlay class
  9497.    * Dialog that allows users to set the home page.
  9498.    * @extends {SettingsDialog}
  9499.    */
  9500.   function HomePageOverlay() {
  9501.     SettingsDialog.call(this, 'homePageOverlay',
  9502.                         loadTimeData.getString('homePageOverlayTabTitle'),
  9503.                         'home-page-overlay',
  9504.                         $('home-page-confirm'), $('home-page-cancel'));
  9505.   }
  9506.  
  9507.   cr.addSingletonGetter(HomePageOverlay);
  9508.  
  9509.   HomePageOverlay.prototype = {
  9510.     __proto__: SettingsDialog.prototype,
  9511.  
  9512.     /**
  9513.      * An autocomplete list that can be attached to the home page URL field.
  9514.      * @type {cr.ui.AutocompleteList}
  9515.      * @private
  9516.      */
  9517.     autocompleteList_: null,
  9518.  
  9519.     /**
  9520.      * Initialize the page.
  9521.      */
  9522.     initializePage: function() {
  9523.       // Call base class implementation to start preference initialization.
  9524.       SettingsDialog.prototype.initializePage.call(this);
  9525.  
  9526.       var self = this;
  9527.       options.Preferences.getInstance().addEventListener(
  9528.           'homepage_is_newtabpage',
  9529.           this.handleHomepageIsNTPPrefChange.bind(this));
  9530.  
  9531.       var urlField = $('homepage-url-field');
  9532.       urlField.addEventListener('keydown', function(event) {
  9533.         // Focus the 'OK' button when the user hits enter since people expect
  9534.         // feedback indicating that they are done editing.
  9535.         if (event.keyIdentifier == 'Enter' && self.autocompleteList_.hidden)
  9536.           $('home-page-confirm').focus();
  9537.       });
  9538.       urlField.addEventListener('change', this.updateFavicon_.bind(this));
  9539.  
  9540.       var suggestionList = new cr.ui.AutocompleteList();
  9541.       suggestionList.autoExpands = true;
  9542.       suggestionList.suggestionUpdateRequestCallback =
  9543.           this.requestAutocompleteSuggestions_.bind(this);
  9544.       $('home-page-overlay').appendChild(suggestionList);
  9545.       this.autocompleteList_ = suggestionList;
  9546.  
  9547.       urlField.addEventListener('focus', function(event) {
  9548.         self.autocompleteList_.attachToInput(urlField);
  9549.       });
  9550.       urlField.addEventListener('blur', function(event) {
  9551.         self.autocompleteList_.detach();
  9552.       });
  9553.  
  9554.       // Text fields may change widths when the window changes size, so make
  9555.       // sure the suggestion list stays in sync.
  9556.       window.addEventListener('resize', function() {
  9557.         self.autocompleteList_.syncWidthToInput();
  9558.       });
  9559.     },
  9560.  
  9561.     /** @override */
  9562.     didShowPage: function() {
  9563.       this.updateFavicon_();
  9564.     },
  9565.  
  9566.     /**
  9567.      * Updates the state of the homepage text input and its controlled setting
  9568.      * indicator when the |homepage_is_newtabpage| pref changes. The input is
  9569.      * enabled only if the homepage is not the NTP. The indicator is always
  9570.      * enabled but treats the input's value as read-only if the homepage is the
  9571.      * NTP.
  9572.      * @param {Event} Pref change event.
  9573.      */
  9574.     handleHomepageIsNTPPrefChange: function(event) {
  9575.       var urlField = $('homepage-url-field');
  9576.       var urlFieldIndicator = $('homepage-url-field-indicator');
  9577.       urlField.setDisabled('homepage-is-ntp', event.value.value);
  9578.       urlFieldIndicator.readOnly = event.value.value;
  9579.     },
  9580.  
  9581.     /**
  9582.      * Updates the background of the url field to show the favicon for the
  9583.      * URL that is currently typed in.
  9584.      * @private
  9585.      */
  9586.     updateFavicon_: function() {
  9587.       var urlField = $('homepage-url-field');
  9588.       urlField.style.backgroundImage = url(getFaviconURL(urlField.value));
  9589.     },
  9590.  
  9591.     /**
  9592.      * Sends an asynchronous request for new autocompletion suggestions for the
  9593.      * the given query. When new suggestions are available, the C++ handler will
  9594.      * call updateAutocompleteSuggestions_.
  9595.      * @param {string} query List of autocomplete suggestions.
  9596.      * @private
  9597.      */
  9598.     requestAutocompleteSuggestions_: function(query) {
  9599.       chrome.send('requestAutocompleteSuggestionsForHomePage', [query]);
  9600.     },
  9601.  
  9602.     /**
  9603.      * Updates the autocomplete suggestion list with the given entries.
  9604.      * @param {Array} pages List of autocomplete suggestions.
  9605.      * @private
  9606.      */
  9607.     updateAutocompleteSuggestions_: function(suggestions) {
  9608.       var list = this.autocompleteList_;
  9609.       // If the trigger for this update was a value being selected from the
  9610.       // current list, do nothing.
  9611.       if (list.targetInput && list.selectedItem &&
  9612.           list.selectedItem.url == list.targetInput.value) {
  9613.         return;
  9614.       }
  9615.       list.suggestions = suggestions;
  9616.     },
  9617.  
  9618.     /**
  9619.      * Sets the 'show home button' and 'home page is new tab page' preferences.
  9620.      * (The home page url preference is set automatically by the SettingsDialog
  9621.      * code.)
  9622.      */
  9623.     handleConfirm: function() {
  9624.       // Strip whitespace.
  9625.       var urlField = $('homepage-url-field');
  9626.       var homePageValue = urlField.value.replace(/\s*/g, '');
  9627.       urlField.value = homePageValue;
  9628.  
  9629.       // Don't save an empty URL for the home page. If the user left the field
  9630.       // empty, switch to the New Tab page.
  9631.       if (!homePageValue)
  9632.         $('homepage-use-ntp').checked = true;
  9633.  
  9634.       SettingsDialog.prototype.handleConfirm.call(this);
  9635.     },
  9636.   };
  9637.  
  9638.   HomePageOverlay.updateAutocompleteSuggestions = function() {
  9639.     var instance = HomePageOverlay.getInstance();
  9640.     instance.updateAutocompleteSuggestions_.apply(instance, arguments);
  9641.   };
  9642.  
  9643.   // Export
  9644.   return {
  9645.     HomePageOverlay: HomePageOverlay
  9646.   };
  9647. });
  9648.  
  9649. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9650. // Use of this source code is governed by a BSD-style license that can be
  9651. // found in the LICENSE file.
  9652.  
  9653. cr.define('options', function() {
  9654.   var OptionsPage = options.OptionsPage;
  9655.  
  9656.   /**
  9657.    * ImportDataOverlay class
  9658.    * Encapsulated handling of the 'Import Data' overlay page.
  9659.    * @class
  9660.    */
  9661.   function ImportDataOverlay() {
  9662.     OptionsPage.call(this,
  9663.                      'importData',
  9664.                      loadTimeData.getString('importDataOverlayTabTitle'),
  9665.                      'import-data-overlay');
  9666.   }
  9667.  
  9668.   cr.addSingletonGetter(ImportDataOverlay);
  9669.  
  9670.   ImportDataOverlay.prototype = {
  9671.     // Inherit from OptionsPage.
  9672.     __proto__: OptionsPage.prototype,
  9673.  
  9674.     /**
  9675.      * Initialize the page.
  9676.      */
  9677.     initializePage: function() {
  9678.       // Call base class implementation to start preference initialization.
  9679.       OptionsPage.prototype.initializePage.call(this);
  9680.  
  9681.       var self = this;
  9682.       var checkboxes =
  9683.           document.querySelectorAll('#import-checkboxes input[type=checkbox]');
  9684.       for (var i = 0; i < checkboxes.length; i++) {
  9685.         checkboxes[i].onchange = function() {
  9686.           self.validateCommitButton_();
  9687.         };
  9688.       }
  9689.  
  9690.       $('import-browsers').onchange = function() {
  9691.         self.updateCheckboxes_();
  9692.         self.validateCommitButton_();
  9693.       };
  9694.  
  9695.       $('import-data-commit').onclick = function() {
  9696.         chrome.send('importData', [
  9697.             String($('import-browsers').selectedIndex),
  9698.             String($('import-history').checked),
  9699.             String($('import-favorites').checked),
  9700.             String($('import-passwords').checked),
  9701.             String($('import-search').checked)]);
  9702.       };
  9703.  
  9704.       $('import-data-cancel').onclick = function() {
  9705.         ImportDataOverlay.dismiss();
  9706.       };
  9707.  
  9708.       $('import-data-show-bookmarks-bar').onchange = function() {
  9709.         // Note: The callback 'toggleShowBookmarksBar' is handled within the
  9710.         // browser options handler -- rather than the import data handler --
  9711.         // as the implementation is shared by several clients.
  9712.         chrome.send('toggleShowBookmarksBar');
  9713.       }
  9714.  
  9715.       $('import-data-confirm').onclick = function() {
  9716.         ImportDataOverlay.dismiss();
  9717.       };
  9718.  
  9719.       // Form controls are disabled until the profile list has been loaded.
  9720.       self.setControlsSensitive_(false);
  9721.     },
  9722.  
  9723.     /**
  9724.      * Set enabled and checked state of the commit button.
  9725.      * @private
  9726.      */
  9727.     validateCommitButton_: function() {
  9728.       var somethingToImport =
  9729.           $('import-history').checked || $('import-favorites').checked ||
  9730.           $('import-passwords').checked || $('import-search').checked;
  9731.       $('import-data-commit').disabled = !somethingToImport;
  9732.     },
  9733.  
  9734.     /**
  9735.      * Sets the sensitivity of all the checkboxes and the commit button.
  9736.      * @private
  9737.      */
  9738.     setControlsSensitive_: function(sensitive) {
  9739.       var checkboxes =
  9740.           document.querySelectorAll('#import-checkboxes input[type=checkbox]');
  9741.       for (var i = 0; i < checkboxes.length; i++)
  9742.         this.setUpCheckboxState_(checkboxes[i], sensitive);
  9743.       $('import-data-commit').disabled = !sensitive;
  9744.     },
  9745.  
  9746.     /**
  9747.      * Set enabled and checked states a checkbox element.
  9748.      * @param {Object} checkbox A checkbox element.
  9749.      * @param {boolean} enabled The enabled state of the chekbox.
  9750.      * @private
  9751.      */
  9752.     setUpCheckboxState_: function(checkbox, enabled) {
  9753.        checkbox.setDisabled('noProfileData', !enabled);
  9754.     },
  9755.  
  9756.     /**
  9757.      * Update the enabled and checked states of all checkboxes.
  9758.      * @private
  9759.      */
  9760.     updateCheckboxes_: function() {
  9761.       var index = $('import-browsers').selectedIndex;
  9762.       var browserProfile;
  9763.       if (this.browserProfiles.length > index)
  9764.         browserProfile = this.browserProfiles[index];
  9765.       var importOptions = ['history', 'favorites', 'passwords', 'search'];
  9766.       for (var i = 0; i < importOptions.length; i++) {
  9767.         var checkbox = $('import-' + importOptions[i]);
  9768.         var enable = browserProfile && browserProfile[importOptions[i]];
  9769.         this.setUpCheckboxState_(checkbox, enable);
  9770.       }
  9771.     },
  9772.  
  9773.     /**
  9774.      * Update the supported browsers popup with given entries.
  9775.      * @param {array} browsers List of supported browsers name.
  9776.      * @private
  9777.      */
  9778.     updateSupportedBrowsers_: function(browsers) {
  9779.       this.browserProfiles = browsers;
  9780.       var browserSelect = $('import-browsers');
  9781.       browserSelect.remove(0);  // Remove the 'Loading...' option.
  9782.       browserSelect.textContent = '';
  9783.       var browserCount = browsers.length;
  9784.  
  9785.       if (browserCount == 0) {
  9786.         var option = new Option(loadTimeData.getString('noProfileFound'), 0);
  9787.         browserSelect.appendChild(option);
  9788.  
  9789.         this.setControlsSensitive_(false);
  9790.       } else {
  9791.         this.setControlsSensitive_(true);
  9792.         for (var i = 0; i < browserCount; i++) {
  9793.           var browser = browsers[i];
  9794.           var option = new Option(browser.name, browser.index);
  9795.           browserSelect.appendChild(option);
  9796.         }
  9797.  
  9798.         this.updateCheckboxes_();
  9799.         this.validateCommitButton_();
  9800.       }
  9801.     },
  9802.  
  9803.     /**
  9804.      * Clear import prefs set when user checks/unchecks a checkbox so that each
  9805.      * checkbox goes back to the default "checked" state (or alternatively, to
  9806.      * the state set by a recommended policy).
  9807.      * @private
  9808.      */
  9809.     clearUserPrefs_: function() {
  9810.       var importPrefs = ['import_history',
  9811.                          'import_bookmarks',
  9812.                          'import_saved_passwords',
  9813.                          'import_search_engine'];
  9814.       for (var i = 0; i < importPrefs.length; i++)
  9815.         Preferences.clearPref(importPrefs[i], true);
  9816.     },
  9817.  
  9818.     /**
  9819.      * Update the dialog layout to reflect success state.
  9820.      * @param {boolean} success If true, show success dialog elements.
  9821.      * @private
  9822.      */
  9823.     updateSuccessState_: function(success) {
  9824.       var sections = document.querySelectorAll('.import-data-configure');
  9825.       for (var i = 0; i < sections.length; i++)
  9826.         sections[i].hidden = success;
  9827.  
  9828.       sections = document.querySelectorAll('.import-data-success');
  9829.       for (var i = 0; i < sections.length; i++)
  9830.         sections[i].hidden = !success;
  9831.     },
  9832.   };
  9833.  
  9834.   ImportDataOverlay.clearUserPrefs = function() {
  9835.     ImportDataOverlay.getInstance().clearUserPrefs_();
  9836.   };
  9837.  
  9838.   /**
  9839.    * Update the supported browsers popup with given entries.
  9840.    * @param {array} list of supported browsers name.
  9841.    */
  9842.   ImportDataOverlay.updateSupportedBrowsers = function(browsers) {
  9843.     ImportDataOverlay.getInstance().updateSupportedBrowsers_(browsers);
  9844.   };
  9845.  
  9846.   /**
  9847.    * Update the UI to reflect whether an import operation is in progress.
  9848.    * @param {boolean} state True if an import operation is in progress.
  9849.    */
  9850.   ImportDataOverlay.setImportingState = function(state) {
  9851.     var checkboxes =
  9852.         document.querySelectorAll('#import-checkboxes input[type=checkbox]');
  9853.     for (var i = 0; i < checkboxes.length; i++)
  9854.         checkboxes[i].setDisabled('Importing', state);
  9855.     if (!state)
  9856.       ImportDataOverlay.getInstance().updateCheckboxes_();
  9857.     $('import-browsers').disabled = state;
  9858.     $('import-throbber').style.visibility = state ? 'visible' : 'hidden';
  9859.     ImportDataOverlay.getInstance().validateCommitButton_();
  9860.   };
  9861.  
  9862.   /**
  9863.    * Remove the import overlay from display.
  9864.    */
  9865.   ImportDataOverlay.dismiss = function() {
  9866.     ImportDataOverlay.clearUserPrefs();
  9867.     OptionsPage.closeOverlay();
  9868.   };
  9869.  
  9870.   /**
  9871.    * Show a message confirming the success of the import operation.
  9872.    */
  9873.   ImportDataOverlay.confirmSuccess = function() {
  9874.     var showBookmarksMessage = $('import-favorites').checked;
  9875.     ImportDataOverlay.setImportingState(false);
  9876.     $('import-find-your-bookmarks').hidden = !showBookmarksMessage;
  9877.     ImportDataOverlay.getInstance().updateSuccessState_(true);
  9878.   };
  9879.  
  9880.   /**
  9881.    * Show the import data overlay.
  9882.    */
  9883.   ImportDataOverlay.show = function() {
  9884.     // Make sure that any previous import success message is hidden, and
  9885.     // we're showing the UI to import further data.
  9886.     ImportDataOverlay.getInstance().updateSuccessState_(false);
  9887.  
  9888.     OptionsPage.navigateToPage('importData');
  9889.   };
  9890.  
  9891.   // Export
  9892.   return {
  9893.     ImportDataOverlay: ImportDataOverlay
  9894.   };
  9895. });
  9896.  
  9897. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9898. // Use of this source code is governed by a BSD-style license that can be
  9899. // found in the LICENSE file.
  9900.  
  9901. ///////////////////////////////////////////////////////////////////////////////
  9902. // AddLanguageOverlay class:
  9903.  
  9904. cr.define('options', function() {
  9905.   /** @const */ var OptionsPage = options.OptionsPage;
  9906.  
  9907.   /**
  9908.    * Encapsulated handling of ChromeOS add language overlay page.
  9909.    * @constructor
  9910.    */
  9911.   function AddLanguageOverlay() {
  9912.     OptionsPage.call(this, 'addLanguage',
  9913.                      loadTimeData.getString('add_button'),
  9914.                      'add-language-overlay-page');
  9915.   }
  9916.  
  9917.   cr.addSingletonGetter(AddLanguageOverlay);
  9918.  
  9919.   AddLanguageOverlay.prototype = {
  9920.     // Inherit AddLanguageOverlay from OptionsPage.
  9921.     __proto__: OptionsPage.prototype,
  9922.  
  9923.     /**
  9924.      * Initializes AddLanguageOverlay page.
  9925.      * Calls base class implementation to starts preference initialization.
  9926.      */
  9927.     initializePage: function() {
  9928.       // Call base class implementation to starts preference initialization.
  9929.       OptionsPage.prototype.initializePage.call(this);
  9930.  
  9931.       // Set up the cancel button.
  9932.       $('add-language-overlay-cancel-button').onclick = function(e) {
  9933.         OptionsPage.closeOverlay();
  9934.       };
  9935.  
  9936.       // Create the language list with which users can add a language.
  9937.       var addLanguageList = $('add-language-overlay-language-list');
  9938.       var languageListData = loadTimeData.getValue('languageList');
  9939.       for (var i = 0; i < languageListData.length; i++) {
  9940.         var language = languageListData[i];
  9941.         var displayText = language.displayName;
  9942.         // If the native name is different, add it.
  9943.         if (language.displayName != language.nativeDisplayName) {
  9944.           displayText += ' - ' + language.nativeDisplayName;
  9945.         }
  9946.  
  9947.         if (cr.isChromeOS) {
  9948.           var button = document.createElement('button');
  9949.           button.className = 'link-button';
  9950.           button.textContent = displayText;
  9951.           button.languageCode = language.code;
  9952.           var li = document.createElement('li');
  9953.           li.languageCode = language.code;
  9954.           li.appendChild(button);
  9955.           addLanguageList.appendChild(li);
  9956.         } else {
  9957.           var option = document.createElement('option');
  9958.           option.value = language.code;
  9959.           option.textContent = displayText;
  9960.           addLanguageList.appendChild(option);
  9961.         }
  9962.       }
  9963.     },
  9964.   };
  9965.  
  9966.   return {
  9967.     AddLanguageOverlay: AddLanguageOverlay
  9968.   };
  9969. });
  9970.  
  9971. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  9972. // Use of this source code is governed by a BSD-style license that can be
  9973. // found in the LICENSE file.
  9974.  
  9975. cr.define('options.dictionary_words', function() {
  9976.   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
  9977.   /** @const */ var InlineEditableItem = options.InlineEditableItem;
  9978.  
  9979.   /**
  9980.    * Creates a new dictionary word list item.
  9981.    * @param {string} dictionaryWord The dictionary word.
  9982.    * @param {function(string)} addDictionaryWordCallback Callback for
  9983.    * adding a dictionary word.
  9984.    * @constructor
  9985.    * @extends {cr.ui.InlineEditableItem}
  9986.    */
  9987.   function DictionaryWordsListItem(dictionaryWord, addDictionaryWordCallback) {
  9988.     var el = cr.doc.createElement('div');
  9989.     el.dictionaryWord_ = dictionaryWord;
  9990.     el.addDictionaryWordCallback_ = addDictionaryWordCallback;
  9991.     DictionaryWordsListItem.decorate(el);
  9992.     return el;
  9993.   }
  9994.  
  9995.   /**
  9996.    * Decorates an HTML element as a dictionary word list item.
  9997.    * @param {HTMLElement} el The element to decorate.
  9998.    */
  9999.   DictionaryWordsListItem.decorate = function(el) {
  10000.     el.__proto__ = DictionaryWordsListItem.prototype;
  10001.     el.decorate();
  10002.   };
  10003.  
  10004.   DictionaryWordsListItem.prototype = {
  10005.     __proto__: InlineEditableItem.prototype,
  10006.  
  10007.     /** @override */
  10008.     decorate: function() {
  10009.       InlineEditableItem.prototype.decorate.call(this);
  10010.       if (this.dictionaryWord_ == '')
  10011.         this.isPlaceholder = true;
  10012.       else
  10013.         this.editable = false;
  10014.  
  10015.       var wordEl = this.createEditableTextCell(this.dictionaryWord_);
  10016.       wordEl.classList.add('weakrtl');
  10017.       wordEl.classList.add('language-dictionary-overlay-word-list-item');
  10018.       this.contentElement.appendChild(wordEl);
  10019.  
  10020.       var wordField = wordEl.querySelector('input');
  10021.       wordField.classList.add('language-dictionary-overlay-word-list-item');
  10022.       if (this.isPlaceholder) {
  10023.         wordField.placeholder =
  10024.             loadTimeData.getString('languageDictionaryOverlayAddWordLabel');
  10025.       }
  10026.  
  10027.       this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
  10028.     },
  10029.  
  10030.     /** @override */
  10031.     get hasBeenEdited() {
  10032.       return this.querySelector('input').value.length > 0;
  10033.     },
  10034.  
  10035.     /**
  10036.      * Adds a word to the dictionary.
  10037.      * @param {Event} e Edit committed event.
  10038.      * @private
  10039.      */
  10040.     onEditCommitted_: function(e) {
  10041.       var input = e.currentTarget.querySelector('input');
  10042.       this.addDictionaryWordCallback_(input.value);
  10043.       input.value = '';
  10044.     },
  10045.   };
  10046.  
  10047.   /**
  10048.    * A list of words in the dictionary.
  10049.    * @extends {cr.ui.InlineEditableItemList}
  10050.    */
  10051.   var DictionaryWordsList = cr.ui.define('list');
  10052.  
  10053.   DictionaryWordsList.prototype = {
  10054.     __proto__: InlineEditableItemList.prototype,
  10055.  
  10056.     /**
  10057.      * The function to notify that the word list has changed.
  10058.      * @type {function()}
  10059.      */
  10060.     onWordListChanged: null,
  10061.  
  10062.     /**
  10063.      * The list of all words in the dictionary. Used to generate a filtered word
  10064.      * list in the |search(searchTerm)| method.
  10065.      * @type {Array}
  10066.      * @private
  10067.      */
  10068.     allWordsList_: null,
  10069.  
  10070.     /**
  10071.      * Add a dictionary word.
  10072.      * @param {string} dictionaryWord The word to add.
  10073.      * @private
  10074.      */
  10075.     addDictionaryWord_: function(dictionaryWord) {
  10076.       this.allWordsList_.push(dictionaryWord);
  10077.       this.dataModel.splice(this.dataModel.length - 1, 0, dictionaryWord);
  10078.       this.onWordListChanged();
  10079.       chrome.send('addDictionaryWord', [dictionaryWord]);
  10080.     },
  10081.  
  10082.     /**
  10083.      * Search the list for the matching words.
  10084.      * @param {string} searchTerm The search term.
  10085.      */
  10086.     search: function(searchTerm) {
  10087.       var filteredWordList = this.allWordsList_.filter(
  10088.           function(element, index, array) {
  10089.             return element.indexOf(searchTerm) > -1;
  10090.           });
  10091.       filteredWordList.push('');
  10092.       this.dataModel = new cr.ui.ArrayDataModel(filteredWordList);
  10093.       this.onWordListChanged();
  10094.     },
  10095.  
  10096.     /**
  10097.      * Set the list of dictionary words.
  10098.      * @param {Array} entries The list of dictionary words.
  10099.      */
  10100.     setWordList: function(entries) {
  10101.       this.allWordsList_ = entries.slice(0);
  10102.       // Empty string is a placeholder for entering new words.
  10103.       entries.push('');
  10104.       this.dataModel = new cr.ui.ArrayDataModel(entries);
  10105.       this.onWordListChanged();
  10106.     },
  10107.  
  10108.     /**
  10109.      * True if the data model contains no words, otherwise false.
  10110.      * @type {boolean}
  10111.      */
  10112.     get empty() {
  10113.       // A data model without dictionary words contains one placeholder for
  10114.       // entering new words.
  10115.       return this.dataModel.length < 2;
  10116.     },
  10117.  
  10118.     /** @override */
  10119.     createItem: function(dictionaryWord) {
  10120.       return new DictionaryWordsListItem(
  10121.           dictionaryWord,
  10122.           this.addDictionaryWord_.bind(this));
  10123.     },
  10124.  
  10125.     /** @override */
  10126.     deleteItemAtIndex: function(index) {
  10127.       // The last element in the data model is an undeletable placeholder for
  10128.       // entering new words.
  10129.       assert(index > -1 && index < this.dataModel.length - 1);
  10130.       var item = this.dataModel.item(index);
  10131.       var allWordsListIndex = this.allWordsList_.indexOf(item);
  10132.       assert(allWordsListIndex > -1);
  10133.       this.allWordsList_.splice(allWordsListIndex, 1);
  10134.       this.dataModel.splice(index, 1);
  10135.       this.onWordListChanged();
  10136.       chrome.send('removeDictionaryWord', [item]);
  10137.     },
  10138.  
  10139.     /** @override */
  10140.     shouldFocusPlaceholder: function() {
  10141.       return false;
  10142.     },
  10143.   };
  10144.  
  10145.   return {
  10146.     DictionaryWordsList: DictionaryWordsList
  10147.   };
  10148.  
  10149. });
  10150.  
  10151.  
  10152.   // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  10153. // Use of this source code is governed by a BSD-style license that can be
  10154. // found in the LICENSE file.
  10155.  
  10156. cr.define('options', function() {
  10157.   /** @const */ var DictionaryWordsList =
  10158.       options.dictionary_words.DictionaryWordsList;
  10159.   /** @const */ var OptionsPage = options.OptionsPage;
  10160.  
  10161.   /**
  10162.    * Adding and removing words in custom spelling dictionary.
  10163.    * @constructor
  10164.    * @extends {options.OptionsPage}
  10165.    */
  10166.   function EditDictionaryOverlay() {
  10167.     OptionsPage.call(this, 'editDictionary',
  10168.                      loadTimeData.getString('languageDictionaryOverlayPage'),
  10169.                      'language-dictionary-overlay-page');
  10170.   }
  10171.  
  10172.   cr.addSingletonGetter(EditDictionaryOverlay);
  10173.  
  10174.   EditDictionaryOverlay.prototype = {
  10175.     __proto__: OptionsPage.prototype,
  10176.  
  10177.     /**
  10178.      * A list of words in the dictionary.
  10179.      * @type {DictionaryWordsList}
  10180.      * @private
  10181.      */
  10182.     wordList_: null,
  10183.  
  10184.     /**
  10185.      * The input field for searching for words in the dictionary.
  10186.      * @type {HTMLElement}
  10187.      * @private
  10188.      */
  10189.     searchField_: null,
  10190.  
  10191.     /**
  10192.      * The paragraph of text that indicates that search returned no results.
  10193.      * @type {HTMLElement}
  10194.      * @private
  10195.      */
  10196.     noMatchesLabel_: null,
  10197.  
  10198.     /**
  10199.      * Initializes the edit dictionary overlay.
  10200.      * @override
  10201.      */
  10202.     initializePage: function() {
  10203.       OptionsPage.prototype.initializePage.call(this);
  10204.  
  10205.       this.wordList_ = $('language-dictionary-overlay-word-list');
  10206.       DictionaryWordsList.decorate(this.wordList_);
  10207.       this.wordList_.onWordListChanged = function() {
  10208.         this.onWordListChanged_();
  10209.       }.bind(this);
  10210.  
  10211.       this.searchField_ = $('language-dictionary-overlay-search-field');
  10212.       this.searchField_.onsearch = function(e) {
  10213.         this.wordList_.search(e.currentTarget.value);
  10214.       }.bind(this);
  10215.  
  10216.       this.noMatchesLabel_ = getRequiredElement(
  10217.           'language-dictionary-overlay-no-matches');
  10218.  
  10219.       $('language-dictionary-overlay-done-button').onclick = function(e) {
  10220.         OptionsPage.closeOverlay();
  10221.       };
  10222.     },
  10223.  
  10224.     /**
  10225.      * Refresh the dictionary words when the page is displayed.
  10226.      * @override
  10227.      */
  10228.     didShowPage: function() {
  10229.       chrome.send('refreshDictionaryWords');
  10230.     },
  10231.  
  10232.     /**
  10233.      * Update the view based on the changes in the word list.
  10234.      * @private
  10235.      */
  10236.     onWordListChanged_: function() {
  10237.       if (this.searchField_.value.length > 0 && this.wordList_.empty) {
  10238.         this.noMatchesLabel_.hidden = false;
  10239.         this.wordList_.classList.add('no-search-matches');
  10240.       } else {
  10241.         this.noMatchesLabel_.hidden = true;
  10242.         this.wordList_.classList.remove('no-search-matches');
  10243.       }
  10244.     },
  10245.   };
  10246.  
  10247.   EditDictionaryOverlay.setWordList = function(entries) {
  10248.     EditDictionaryOverlay.getInstance().wordList_.setWordList(entries);
  10249.   };
  10250.  
  10251.   EditDictionaryOverlay.getWordListForTesting = function() {
  10252.     return EditDictionaryOverlay.getInstance().wordList_;
  10253.   };
  10254.  
  10255.   return {
  10256.     EditDictionaryOverlay: EditDictionaryOverlay
  10257.   };
  10258. });
  10259.  
  10260. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  10261. // Use of this source code is governed by a BSD-style license that can be
  10262. // found in the LICENSE file.
  10263.  
  10264. cr.define('options', function() {
  10265.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  10266.   /** @const */ var DeletableItem = options.DeletableItem;
  10267.   /** @const */ var DeletableItemList = options.DeletableItemList;
  10268.   /** @const */ var List = cr.ui.List;
  10269.   /** @const */ var ListItem = cr.ui.ListItem;
  10270.   /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  10271.  
  10272.   /**
  10273.    * Creates a new Language list item.
  10274.    * @param {Object} languageInfo The information of the language.
  10275.    * @constructor
  10276.    * @extends {DeletableItem.ListItem}
  10277.    */
  10278.   function LanguageListItem(languageInfo) {
  10279.     var el = cr.doc.createElement('li');
  10280.     el.__proto__ = LanguageListItem.prototype;
  10281.     el.language_ = languageInfo;
  10282.     el.decorate();
  10283.     return el;
  10284.   };
  10285.  
  10286.   LanguageListItem.prototype = {
  10287.     __proto__: DeletableItem.prototype,
  10288.  
  10289.     /**
  10290.      * The language code of this language.
  10291.      * @type {String}
  10292.      * @private
  10293.      */
  10294.     languageCode_: null,
  10295.  
  10296.     /** @override */
  10297.     decorate: function() {
  10298.       DeletableItem.prototype.decorate.call(this);
  10299.  
  10300.       var languageCode = this.language_.code;
  10301.       var languageOptions = options.LanguageOptions.getInstance();
  10302.       this.deletable = languageOptions.languageIsDeletable(languageCode);
  10303.       this.languageCode = languageCode;
  10304.       this.languageName = cr.doc.createElement('div');
  10305.       this.languageName.className = 'language-name';
  10306.       this.languageName.dir = this.language_.textDirection;
  10307.       this.languageName.textContent = this.language_.displayName;
  10308.       this.contentElement.appendChild(this.languageName);
  10309.       this.title = this.language_.nativeDisplayName;
  10310.       this.draggable = true;
  10311.     },
  10312.   };
  10313.  
  10314.   /**
  10315.    * Creates a new language list.
  10316.    * @param {Object=} opt_propertyBag Optional properties.
  10317.    * @constructor
  10318.    * @extends {cr.ui.List}
  10319.    */
  10320.   var LanguageList = cr.ui.define('list');
  10321.  
  10322.   /**
  10323.    * Gets information of a language from the given language code.
  10324.    * @param {string} languageCode Language code (ex. "fr").
  10325.    */
  10326.   LanguageList.getLanguageInfoFromLanguageCode = function(languageCode) {
  10327.     // Build the language code to language info dictionary at first time.
  10328.     if (!this.languageCodeToLanguageInfo_) {
  10329.       this.languageCodeToLanguageInfo_ = {};
  10330.       var languageList = loadTimeData.getValue('languageList');
  10331.       for (var i = 0; i < languageList.length; i++) {
  10332.         var languageInfo = languageList[i];
  10333.         this.languageCodeToLanguageInfo_[languageInfo.code] = languageInfo;
  10334.       }
  10335.     }
  10336.  
  10337.     return this.languageCodeToLanguageInfo_[languageCode];
  10338.   }
  10339.  
  10340.   /**
  10341.    * Returns true if the given language code is valid.
  10342.    * @param {string} languageCode Language code (ex. "fr").
  10343.    */
  10344.   LanguageList.isValidLanguageCode = function(languageCode) {
  10345.     // Having the display name for the language code means that the
  10346.     // language code is valid.
  10347.     if (LanguageList.getLanguageInfoFromLanguageCode(languageCode)) {
  10348.       return true;
  10349.     }
  10350.     return false;
  10351.   }
  10352.  
  10353.   LanguageList.prototype = {
  10354.     __proto__: DeletableItemList.prototype,
  10355.  
  10356.     // The list item being dragged.
  10357.     draggedItem: null,
  10358.     // The drop position information: "below" or "above".
  10359.     dropPos: null,
  10360.     // The preference is a CSV string that describes preferred languages
  10361.     // in Chrome OS. The language list is used for showing the language
  10362.     // list in "Language and Input" options page.
  10363.     preferredLanguagesPref: 'settings.language.preferred_languages',
  10364.     // The preference is a CSV string that describes accept languages used
  10365.     // for content negotiation. To be more precise, the list will be used
  10366.     // in "Accept-Language" header in HTTP requests.
  10367.     acceptLanguagesPref: 'intl.accept_languages',
  10368.  
  10369.     /** @override */
  10370.     decorate: function() {
  10371.       DeletableItemList.prototype.decorate.call(this);
  10372.       this.selectionModel = new ListSingleSelectionModel;
  10373.  
  10374.       // HACK(arv): http://crbug.com/40902
  10375.       window.addEventListener('resize', this.redraw.bind(this));
  10376.  
  10377.       // Listen to pref change.
  10378.       if (cr.isChromeOS) {
  10379.         Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
  10380.             this.handlePreferredLanguagesPrefChange_.bind(this));
  10381.       } else {
  10382.         Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
  10383.             this.handleAcceptLanguagesPrefChange_.bind(this));
  10384.       }
  10385.  
  10386.       // Listen to drag and drop events.
  10387.       this.addEventListener('dragstart', this.handleDragStart_.bind(this));
  10388.       this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
  10389.       this.addEventListener('dragover', this.handleDragOver_.bind(this));
  10390.       this.addEventListener('drop', this.handleDrop_.bind(this));
  10391.       this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
  10392.     },
  10393.  
  10394.     createItem: function(languageCode) {
  10395.       languageInfo = LanguageList.getLanguageInfoFromLanguageCode(languageCode);
  10396.       return new LanguageListItem(languageInfo);
  10397.     },
  10398.  
  10399.     /*
  10400.      * For each item, determines whether it's deletable.
  10401.      */
  10402.     updateDeletable: function() {
  10403.       var items = this.items;
  10404.       for (var i = 0; i < items.length; ++i) {
  10405.         var item = items[i];
  10406.         var languageCode = item.languageCode;
  10407.         var languageOptions = options.LanguageOptions.getInstance();
  10408.         item.deletable = languageOptions.languageIsDeletable(languageCode);
  10409.       }
  10410.     },
  10411.  
  10412.     /*
  10413.      * Adds a language to the language list.
  10414.      * @param {string} languageCode language code (ex. "fr").
  10415.      */
  10416.     addLanguage: function(languageCode) {
  10417.       // It shouldn't happen but ignore the language code if it's
  10418.       // null/undefined, or already present.
  10419.       if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
  10420.         return;
  10421.       }
  10422.       this.dataModel.push(languageCode);
  10423.       // Select the last item, which is the language added.
  10424.       this.selectionModel.selectedIndex = this.dataModel.length - 1;
  10425.  
  10426.       this.savePreference_();
  10427.     },
  10428.  
  10429.     /*
  10430.      * Gets the language codes of the currently listed languages.
  10431.      */
  10432.     getLanguageCodes: function() {
  10433.       return this.dataModel.slice();
  10434.     },
  10435.  
  10436.     /*
  10437.      * Clears the selection
  10438.      */
  10439.     clearSelection: function() {
  10440.       this.selectionModel.unselectAll();
  10441.     },
  10442.  
  10443.     /*
  10444.      * Gets the language code of the selected language.
  10445.      */
  10446.     getSelectedLanguageCode: function() {
  10447.       return this.selectedItem;
  10448.     },
  10449.  
  10450.     /*
  10451.      * Selects the language by the given language code.
  10452.      * @returns {boolean} True if the operation is successful.
  10453.      */
  10454.     selectLanguageByCode: function(languageCode) {
  10455.       var index = this.dataModel.indexOf(languageCode);
  10456.       if (index >= 0) {
  10457.         this.selectionModel.selectedIndex = index;
  10458.         return true;
  10459.       }
  10460.       return false;
  10461.     },
  10462.  
  10463.     /** @override */
  10464.     deleteItemAtIndex: function(index) {
  10465.       if (index >= 0) {
  10466.         this.dataModel.splice(index, 1);
  10467.         // Once the selected item is removed, there will be no selected item.
  10468.         // Select the item pointed by the lead index.
  10469.         index = this.selectionModel.leadIndex;
  10470.         this.savePreference_();
  10471.       }
  10472.       return index;
  10473.     },
  10474.  
  10475.     /*
  10476.      * Computes the target item of drop event.
  10477.      * @param {Event} e The drop or dragover event.
  10478.      * @private
  10479.      */
  10480.     getTargetFromDropEvent_: function(e) {
  10481.       var target = e.target;
  10482.       // e.target may be an inner element of the list item
  10483.       while (target != null && !(target instanceof ListItem)) {
  10484.         target = target.parentNode;
  10485.       }
  10486.       return target;
  10487.     },
  10488.  
  10489.     /*
  10490.      * Handles the dragstart event.
  10491.      * @param {Event} e The dragstart event.
  10492.      * @private
  10493.      */
  10494.     handleDragStart_: function(e) {
  10495.       var target = e.target;
  10496.       // ListItem should be the only draggable element type in the page,
  10497.       // but just in case.
  10498.       if (target instanceof ListItem) {
  10499.         this.draggedItem = target;
  10500.         e.dataTransfer.effectAllowed = 'move';
  10501.         // We need to put some kind of data in the drag or it will be
  10502.         // ignored.  Use the display name in case the user drags to a text
  10503.         // field or the desktop.
  10504.         e.dataTransfer.setData('text/plain', target.title);
  10505.       }
  10506.     },
  10507.  
  10508.     /*
  10509.      * Handles the dragenter event.
  10510.      * @param {Event} e The dragenter event.
  10511.      * @private
  10512.      */
  10513.     handleDragEnter_: function(e) {
  10514.       e.preventDefault();
  10515.     },
  10516.  
  10517.     /*
  10518.      * Handles the dragover event.
  10519.      * @param {Event} e The dragover event.
  10520.      * @private
  10521.      */
  10522.     handleDragOver_: function(e) {
  10523.       var dropTarget = this.getTargetFromDropEvent_(e);
  10524.       // Determines whether the drop target is to accept the drop.
  10525.       // The drop is only successful on another ListItem.
  10526.       if (!(dropTarget instanceof ListItem) ||
  10527.           dropTarget == this.draggedItem) {
  10528.         this.hideDropMarker_();
  10529.         return;
  10530.       }
  10531.       // Compute the drop postion. Should we move the dragged item to
  10532.       // below or above the drop target?
  10533.       var rect = dropTarget.getBoundingClientRect();
  10534.       var dy = e.clientY - rect.top;
  10535.       var yRatio = dy / rect.height;
  10536.       var dropPos = yRatio <= .5 ? 'above' : 'below';
  10537.       this.dropPos = dropPos;
  10538.       this.showDropMarker_(dropTarget, dropPos);
  10539.       e.preventDefault();
  10540.     },
  10541.  
  10542.     /*
  10543.      * Handles the drop event.
  10544.      * @param {Event} e The drop event.
  10545.      * @private
  10546.      */
  10547.     handleDrop_: function(e) {
  10548.       var dropTarget = this.getTargetFromDropEvent_(e);
  10549.       this.hideDropMarker_();
  10550.  
  10551.       // Delete the language from the original position.
  10552.       var languageCode = this.draggedItem.languageCode;
  10553.       var originalIndex = this.dataModel.indexOf(languageCode);
  10554.       this.dataModel.splice(originalIndex, 1);
  10555.       // Insert the language to the new position.
  10556.       var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
  10557.       if (this.dropPos == 'below')
  10558.         newIndex += 1;
  10559.       this.dataModel.splice(newIndex, 0, languageCode);
  10560.       // The cursor should move to the moved item.
  10561.       this.selectionModel.selectedIndex = newIndex;
  10562.       // Save the preference.
  10563.       this.savePreference_();
  10564.     },
  10565.  
  10566.     /*
  10567.      * Handles the dragleave event.
  10568.      * @param {Event} e The dragleave event
  10569.      * @private
  10570.      */
  10571.     handleDragLeave_: function(e) {
  10572.       this.hideDropMarker_();
  10573.     },
  10574.  
  10575.     /*
  10576.      * Shows and positions the marker to indicate the drop target.
  10577.      * @param {HTMLElement} target The current target list item of drop
  10578.      * @param {string} pos 'below' or 'above'
  10579.      * @private
  10580.      */
  10581.     showDropMarker_: function(target, pos) {
  10582.       window.clearTimeout(this.hideDropMarkerTimer_);
  10583.       var marker = $('language-options-list-dropmarker');
  10584.       var rect = target.getBoundingClientRect();
  10585.       var markerHeight = 8;
  10586.       if (pos == 'above') {
  10587.         marker.style.top = (rect.top - markerHeight / 2) + 'px';
  10588.       } else {
  10589.         marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
  10590.       }
  10591.       marker.style.width = rect.width + 'px';
  10592.       marker.style.left = rect.left + 'px';
  10593.       marker.style.display = 'block';
  10594.     },
  10595.  
  10596.     /*
  10597.      * Hides the drop marker.
  10598.      * @private
  10599.      */
  10600.     hideDropMarker_: function() {
  10601.       // Hide the marker in a timeout to reduce flickering as we move between
  10602.       // valid drop targets.
  10603.       window.clearTimeout(this.hideDropMarkerTimer_);
  10604.       this.hideDropMarkerTimer_ = window.setTimeout(function() {
  10605.         $('language-options-list-dropmarker').style.display = '';
  10606.       }, 100);
  10607.     },
  10608.  
  10609.     /**
  10610.      * Handles preferred languages pref change.
  10611.      * @param {Event} e The change event object.
  10612.      * @private
  10613.      */
  10614.     handlePreferredLanguagesPrefChange_: function(e) {
  10615.       var languageCodesInCsv = e.value.value;
  10616.       var languageCodes = languageCodesInCsv.split(',');
  10617.  
  10618.       // Add the UI language to the initial list of languages.  This is to avoid
  10619.       // a bug where the UI language would be removed from the preferred
  10620.       // language list by sync on first login.
  10621.       // See: crosbug.com/14283
  10622.       languageCodes.push(navigator.language);
  10623.       languageCodes = this.filterBadLanguageCodes_(languageCodes);
  10624.       this.load_(languageCodes);
  10625.     },
  10626.  
  10627.     /**
  10628.      * Handles accept languages pref change.
  10629.      * @param {Event} e The change event object.
  10630.      * @private
  10631.      */
  10632.     handleAcceptLanguagesPrefChange_: function(e) {
  10633.       var languageCodesInCsv = e.value.value;
  10634.       var languageCodes = this.filterBadLanguageCodes_(
  10635.           languageCodesInCsv.split(','));
  10636.       this.load_(languageCodes);
  10637.     },
  10638.  
  10639.     /**
  10640.      * Loads given language list.
  10641.      * @param {Array} languageCodes List of language codes.
  10642.      * @private
  10643.      */
  10644.     load_: function(languageCodes) {
  10645.       // Preserve the original selected index. See comments below.
  10646.       var originalSelectedIndex = (this.selectionModel ?
  10647.                                    this.selectionModel.selectedIndex : -1);
  10648.       this.dataModel = new ArrayDataModel(languageCodes);
  10649.       if (originalSelectedIndex >= 0 &&
  10650.           originalSelectedIndex < this.dataModel.length) {
  10651.         // Restore the original selected index if the selected index is
  10652.         // valid after the data model is loaded. This is neeeded to keep
  10653.         // the selected language after the languge is added or removed.
  10654.         this.selectionModel.selectedIndex = originalSelectedIndex;
  10655.         // The lead index should be updated too.
  10656.         this.selectionModel.leadIndex = originalSelectedIndex;
  10657.       } else if (this.dataModel.length > 0) {
  10658.         // Otherwise, select the first item if it's not empty.
  10659.         // Note that ListSingleSelectionModel won't select an item
  10660.         // automatically, hence we manually select the first item here.
  10661.         this.selectionModel.selectedIndex = 0;
  10662.       }
  10663.     },
  10664.  
  10665.     /**
  10666.      * Saves the preference.
  10667.      */
  10668.     savePreference_: function() {
  10669.       // Encode the language codes into a CSV string.
  10670.       if (cr.isChromeOS)
  10671.         Preferences.setStringPref(this.preferredLanguagesPref,
  10672.                                   this.dataModel.slice().join(','), true);
  10673.       // Save the same language list as accept languages preference as
  10674.       // well, but we need to expand the language list, to make it more
  10675.       // acceptable. For instance, some web sites don't understand 'en-US'
  10676.       // but 'en'. See crosbug.com/9884.
  10677.       var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
  10678.       Preferences.setStringPref(this.acceptLanguagesPref,
  10679.                                 acceptLanguages.join(','), true);
  10680.       cr.dispatchSimpleEvent(this, 'save');
  10681.     },
  10682.  
  10683.     /**
  10684.      * Expands language codes to make these more suitable for Accept-Language.
  10685.      * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
  10686.      * 'en' won't appear twice as this function eliminates duplicates.
  10687.      * @param {Array} languageCodes List of language codes.
  10688.      * @private
  10689.      */
  10690.     expandLanguageCodes: function(languageCodes) {
  10691.       var expandedLanguageCodes = [];
  10692.       var seen = {};  // Used to eliminiate duplicates.
  10693.       for (var i = 0; i < languageCodes.length; i++) {
  10694.         var languageCode = languageCodes[i];
  10695.         if (!(languageCode in seen)) {
  10696.           expandedLanguageCodes.push(languageCode);
  10697.           seen[languageCode] = true;
  10698.         }
  10699.         var parts = languageCode.split('-');
  10700.         if (!(parts[0] in seen)) {
  10701.           expandedLanguageCodes.push(parts[0]);
  10702.           seen[parts[0]] = true;
  10703.         }
  10704.       }
  10705.       return expandedLanguageCodes;
  10706.     },
  10707.  
  10708.     /**
  10709.      * Filters bad language codes in case bad language codes are
  10710.      * stored in the preference. Removes duplicates as well.
  10711.      * @param {Array} languageCodes List of language codes.
  10712.      * @private
  10713.      */
  10714.     filterBadLanguageCodes_: function(languageCodes) {
  10715.       var filteredLanguageCodes = [];
  10716.       var seen = {};
  10717.       for (var i = 0; i < languageCodes.length; i++) {
  10718.         // Check if the the language code is valid, and not
  10719.         // duplicate. Otherwise, skip it.
  10720.         if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
  10721.             !(languageCodes[i] in seen)) {
  10722.           filteredLanguageCodes.push(languageCodes[i]);
  10723.           seen[languageCodes[i]] = true;
  10724.         }
  10725.       }
  10726.       return filteredLanguageCodes;
  10727.     },
  10728.   };
  10729.  
  10730.   return {
  10731.     LanguageList: LanguageList,
  10732.     LanguageListItem: LanguageListItem
  10733.   };
  10734. });
  10735.  
  10736. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  10737. // Use of this source code is governed by a BSD-style license that can be
  10738. // found in the LICENSE file.
  10739.  
  10740. // TODO(kochi): Generalize the notification as a component and put it
  10741. // in js/cr/ui/notification.js .
  10742.  
  10743. cr.define('options', function() {
  10744.   /** @const */ var OptionsPage = options.OptionsPage;
  10745.   /** @const */ var LanguageList = options.LanguageList;
  10746.  
  10747.   // Some input methods like Chinese Pinyin have config pages.
  10748.   // This is the map of the input method names to their config page names.
  10749.   /** @const */ var INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = {
  10750.     'mozc': 'languageMozc',
  10751.     'mozc-chewing': 'languageChewing',
  10752.     'mozc-dv': 'languageMozc',
  10753.     'mozc-hangul': 'languageHangul',
  10754.     'mozc-jp': 'languageMozc',
  10755.     'pinyin': 'languagePinyin',
  10756.     'pinyin-dv': 'languagePinyin',
  10757.   };
  10758.  
  10759.   /////////////////////////////////////////////////////////////////////////////
  10760.   // LanguageOptions class:
  10761.  
  10762.   /**
  10763.    * Encapsulated handling of ChromeOS language options page.
  10764.    * @constructor
  10765.    */
  10766.   function LanguageOptions(model) {
  10767.     OptionsPage.call(this, 'languages',
  10768.                      loadTimeData.getString('languagePageTabTitle'),
  10769.                      'languagePage');
  10770.   }
  10771.  
  10772.   cr.addSingletonGetter(LanguageOptions);
  10773.  
  10774.   // Inherit LanguageOptions from OptionsPage.
  10775.   LanguageOptions.prototype = {
  10776.     __proto__: OptionsPage.prototype,
  10777.  
  10778.     /* For recording the prospective language (the next locale after relaunch).
  10779.      * @type {?string}
  10780.      * @private
  10781.      */
  10782.     prospectiveUiLanguageCode_: null,
  10783.  
  10784.     /**
  10785.      * Initializes LanguageOptions page.
  10786.      * Calls base class implementation to start preference initialization.
  10787.      */
  10788.     initializePage: function() {
  10789.       OptionsPage.prototype.initializePage.call(this);
  10790.  
  10791.       var languageOptionsList = $('language-options-list');
  10792.       LanguageList.decorate(languageOptionsList);
  10793.  
  10794.       languageOptionsList.addEventListener('change',
  10795.           this.handleLanguageOptionsListChange_.bind(this));
  10796.       languageOptionsList.addEventListener('save',
  10797.           this.handleLanguageOptionsListSave_.bind(this));
  10798.  
  10799.       this.prospectiveUiLanguageCode_ =
  10800.           loadTimeData.getString('prospectiveUiLanguageCode');
  10801.       this.addEventListener('visibleChange',
  10802.                             this.handleVisibleChange_.bind(this));
  10803.  
  10804.       if (cr.isChromeOS) {
  10805.         $('chewing-confirm').onclick = $('hangul-confirm').onclick =
  10806.             $('mozc-confirm').onclick = $('pinyin-confirm').onclick =
  10807.                 OptionsPage.closeOverlay.bind(OptionsPage);
  10808.  
  10809.         this.initializeInputMethodList_();
  10810.         this.initializeLanguageCodeToInputMethodIdsMap_();
  10811.       }
  10812.       Preferences.getInstance().addEventListener(this.spellCheckDictionaryPref,
  10813.           this.handleSpellCheckDictionaryPrefChange_.bind(this));
  10814.  
  10815.       // Set up add button.
  10816.       $('language-options-add-button').onclick = function(e) {
  10817.         // Add the language without showing the overlay if it's specified in
  10818.         // the URL hash (ex. lang_add=ja).  Used for automated testing.
  10819.         var match = document.location.hash.match(/\blang_add=([\w-]+)/);
  10820.         if (match) {
  10821.           var addLanguageCode = match[1];
  10822.           $('language-options-list').addLanguage(addLanguageCode);
  10823.         } else {
  10824.           OptionsPage.navigateToPage('addLanguage');
  10825.         }
  10826.       };
  10827.  
  10828.       if (!cr.isMac) {
  10829.         // Set up the button for editing custom spelling dictionary.
  10830.         $('edit-dictionary-button') .onclick = function(e) {
  10831.           OptionsPage.navigateToPage('editDictionary');
  10832.         };
  10833.       }
  10834.  
  10835.       if (cr.isChromeOS) {
  10836.         // Listen to user clicks on the add language list.
  10837.         var addLanguageList = $('add-language-overlay-language-list');
  10838.         addLanguageList.addEventListener(
  10839.             'click',
  10840.             this.handleAddLanguageListClick_.bind(this));
  10841.         $('language-options-extension-ime-button').addEventListener(
  10842.             'click',
  10843.             this.handleExtensionImeButtonClick_.bind(this));
  10844.       } else {
  10845.         // Listen to add language dialog ok button.
  10846.         var addLanguageOkButton = $('add-language-overlay-ok-button');
  10847.         addLanguageOkButton.addEventListener(
  10848.             'click',
  10849.             this.handleAddLanguageOkButtonClick_.bind(this));
  10850.  
  10851.         // Show experimental features if enabled.
  10852.         if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
  10853.           $('auto-spell-correction-option').hidden = false;
  10854.  
  10855.         // Handle spell check enable/disable.
  10856.         if (!cr.isMac) {
  10857.           Preferences.getInstance().addEventListener(
  10858.               this.enableSpellCheckPref,
  10859.               this.updateEnableSpellCheck_.bind(this));
  10860.         }
  10861.       }
  10862.  
  10863.       // Handle clicks on "Use this language for spell checking" button.
  10864.       if (!cr.isMac) {
  10865.         var spellCheckLanguageButton = getRequiredElement(
  10866.             'language-options-spell-check-language-button');
  10867.         spellCheckLanguageButton.addEventListener(
  10868.             'click',
  10869.             this.handleSpellCheckLanguageButtonClick_.bind(this));
  10870.       }
  10871.  
  10872.       if (cr.isChromeOS) {
  10873.         $('language-options-ui-restart-button').onclick = function() {
  10874.           chrome.send('uiLanguageRestart');
  10875.         };
  10876.       }
  10877.  
  10878.       $('language-confirm').onclick =
  10879.           OptionsPage.closeOverlay.bind(OptionsPage);
  10880.     },
  10881.  
  10882.     // The preference is a boolean that enables/disables spell checking.
  10883.     enableSpellCheckPref: 'browser.enable_spellchecking',
  10884.     // The preference is a CSV string that describes preload engines
  10885.     // (i.e. active input methods).
  10886.     preloadEnginesPref: 'settings.language.preload_engines',
  10887.     // The list of preload engines, like ['mozc', 'pinyin'].
  10888.     preloadEngines_: [],
  10889.     // The preference that lists the extension IMEs that are filtered out of
  10890.     // the language menu.
  10891.     filteredExtensionImesPref: 'settings.language.filtered_extension_imes',
  10892.     // The list of extension IMEs that are filtered out of the language menu.
  10893.     filteredExtensionImes_: [],
  10894.     // The preference is a string that describes the spell check
  10895.     // dictionary language, like "en-US".
  10896.     spellCheckDictionaryPref: 'spellcheck.dictionary',
  10897.     spellCheckDictionary_: '',
  10898.     // The map of language code to input method IDs, like:
  10899.     // {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
  10900.     languageCodeToInputMethodIdsMap_: {},
  10901.  
  10902.     /**
  10903.      * Initializes the input method list.
  10904.      */
  10905.     initializeInputMethodList_: function() {
  10906.       var inputMethodList = $('language-options-input-method-list');
  10907.       var inputMethodListData = loadTimeData.getValue('inputMethodList');
  10908.       var inputMethodPrototype = $('language-options-input-method-proto');
  10909.  
  10910.       // Add all input methods, but make all of them invisible here. We'll
  10911.       // change the visibility in handleLanguageOptionsListChange_() based
  10912.       // on the selected language. Note that we only have less than 100
  10913.       // input methods, so creating DOM nodes at once here should be ok.
  10914.       for (var i = 0; i < inputMethodListData.length; i++) {
  10915.         var inputMethod = inputMethodListData[i];
  10916.         var element = inputMethodPrototype.cloneNode(true);
  10917.         element.id = '';
  10918.         element.languageCodeSet = inputMethod.languageCodeSet;
  10919.         var input = element.querySelectorAll('input')[0];
  10920.         input.inputMethodId = inputMethod.id;
  10921.         var span = element.querySelectorAll('span')[0];
  10922.         span.textContent = inputMethod.displayName;
  10923.  
  10924.         // Listen to user clicks.
  10925.         input.addEventListener('click',
  10926.                                this.handleCheckboxClick_.bind(this));
  10927.  
  10928.         // Add the configure button if the config page is present for this
  10929.         // input method.
  10930.         if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) {
  10931.           var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id];
  10932.           var button = this.createConfigureInputMethodButton_(inputMethod.id,
  10933.                                                               pageName);
  10934.           element.appendChild(button);
  10935.         }
  10936.         inputMethodList.appendChild(element);
  10937.       }
  10938.  
  10939.       var extensionImeList = loadTimeData.getValue('extensionImeList');
  10940.       for (var i = 0; i < extensionImeList.length; i++) {
  10941.         var inputMethod = extensionImeList[i];
  10942.         var element = inputMethodPrototype.cloneNode(true);
  10943.         element.id = '';
  10944.         element.languageCodeSet = {};
  10945.         var input = element.querySelectorAll('input')[0];
  10946.         input.inputMethodId = inputMethod.id;
  10947.         var span = element.querySelectorAll('span')[0];
  10948.         span.textContent = inputMethod.displayName;
  10949.  
  10950.         input.addEventListener('click',
  10951.                                this.handleExtensionCheckboxClick_.bind(this));
  10952.  
  10953.         inputMethodList.appendChild(element);
  10954.       }
  10955.  
  10956.       // Listen to pref change once the input method list is initialized.
  10957.       Preferences.getInstance().addEventListener(
  10958.           this.preloadEnginesPref,
  10959.           this.handlePreloadEnginesPrefChange_.bind(this));
  10960.       Preferences.getInstance().addEventListener(
  10961.           this.filteredExtensionImesPref,
  10962.           this.handleFilteredExtensionsPrefChange_.bind(this));
  10963.     },
  10964.  
  10965.     /**
  10966.      * Creates a configure button for the given input method ID.
  10967.      * @param {string} inputMethodId Input method ID (ex. "pinyin").
  10968.      * @param {string} pageName Name of the config page (ex. "languagePinyin").
  10969.      * @private
  10970.      */
  10971.     createConfigureInputMethodButton_: function(inputMethodId, pageName) {
  10972.       var button = document.createElement('button');
  10973.       button.textContent = loadTimeData.getString('configure');
  10974.       button.onclick = function(e) {
  10975.         // Prevent the default action (i.e. changing the checked property
  10976.         // of the checkbox). The button click here should not be handled
  10977.         // as checkbox click.
  10978.         e.preventDefault();
  10979.         chrome.send('inputMethodOptionsOpen', [inputMethodId]);
  10980.         OptionsPage.navigateToPage(pageName);
  10981.       };
  10982.       return button;
  10983.     },
  10984.  
  10985.     /**
  10986.      * Handles OptionsPage's visible property change event.
  10987.      * @param {Event} e Property change event.
  10988.      * @private
  10989.      */
  10990.     handleVisibleChange_: function(e) {
  10991.       if (this.visible) {
  10992.         $('language-options-list').redraw();
  10993.         chrome.send('languageOptionsOpen');
  10994.       }
  10995.     },
  10996.  
  10997.     /**
  10998.      * Handles languageOptionsList's change event.
  10999.      * @param {Event} e Change event.
  11000.      * @private
  11001.      */
  11002.     handleLanguageOptionsListChange_: function(e) {
  11003.       var languageOptionsList = $('language-options-list');
  11004.       var languageCode = languageOptionsList.getSelectedLanguageCode();
  11005.  
  11006.       // If there's no selection, just return.
  11007.       if (!languageCode)
  11008.         return;
  11009.  
  11010.       // Select the language if it's specified in the URL hash (ex. lang=ja).
  11011.       // Used for automated testing.
  11012.       var match = document.location.hash.match(/\blang=([\w-]+)/);
  11013.       if (match) {
  11014.         var specifiedLanguageCode = match[1];
  11015.         if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
  11016.           languageCode = specifiedLanguageCode;
  11017.         }
  11018.       }
  11019.  
  11020.       if (cr.isWindows || cr.isChromeOS)
  11021.         this.updateUiLanguageButton_(languageCode);
  11022.  
  11023.       if (!cr.isMac) {
  11024.         this.updateSelectedLanguageName_(languageCode);
  11025.         this.updateSpellCheckLanguageButton_(languageCode);
  11026.       }
  11027.  
  11028.       if (cr.isChromeOS)
  11029.         this.updateInputMethodList_(languageCode);
  11030.  
  11031.       this.updateLanguageListInAddLanguageOverlay_();
  11032.     },
  11033.  
  11034.     /**
  11035.      * Happens when a user changes back to the language they're currently using.
  11036.      */
  11037.     currentLocaleWasReselected: function() {
  11038.       this.updateUiLanguageButton_(
  11039.           loadTimeData.getString('currentUiLanguageCode'));
  11040.     },
  11041.  
  11042.     /**
  11043.      * Handles languageOptionsList's save event.
  11044.      * @param {Event} e Save event.
  11045.      * @private
  11046.      */
  11047.     handleLanguageOptionsListSave_: function(e) {
  11048.       if (cr.isChromeOS) {
  11049.         // Sort the preload engines per the saved languages before save.
  11050.         this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
  11051.         this.savePreloadEnginesPref_();
  11052.       }
  11053.     },
  11054.  
  11055.     /**
  11056.      * Sorts preloadEngines_ by languageOptionsList's order.
  11057.      * @param {Array} preloadEngines List of preload engines.
  11058.      * @return {Array} Returns sorted preloadEngines.
  11059.      * @private
  11060.      */
  11061.     sortPreloadEngines_: function(preloadEngines) {
  11062.       // For instance, suppose we have two languages and associated input
  11063.       // methods:
  11064.       //
  11065.       // - Korean: hangul
  11066.       // - Chinese: pinyin
  11067.       //
  11068.       // The preloadEngines preference should look like "hangul,pinyin".
  11069.       // If the user reverse the order, the preference should be reorderd
  11070.       // to "pinyin,hangul".
  11071.       var languageOptionsList = $('language-options-list');
  11072.       var languageCodes = languageOptionsList.getLanguageCodes();
  11073.  
  11074.       // Convert the list into a dictonary for simpler lookup.
  11075.       var preloadEngineSet = {};
  11076.       for (var i = 0; i < preloadEngines.length; i++) {
  11077.         preloadEngineSet[preloadEngines[i]] = true;
  11078.       }
  11079.  
  11080.       // Create the new preload engine list per the language codes.
  11081.       var newPreloadEngines = [];
  11082.       for (var i = 0; i < languageCodes.length; i++) {
  11083.         var languageCode = languageCodes[i];
  11084.         var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
  11085.             languageCode];
  11086.         // Check if we have active input methods associated with the language.
  11087.         for (var j = 0; j < inputMethodIds.length; j++) {
  11088.           var inputMethodId = inputMethodIds[j];
  11089.           if (inputMethodId in preloadEngineSet) {
  11090.             // If we have, add it to the new engine list.
  11091.             newPreloadEngines.push(inputMethodId);
  11092.             // And delete it from the set. This is necessary as one input
  11093.             // method can be associated with more than one language thus
  11094.             // we should avoid having duplicates in the new list.
  11095.             delete preloadEngineSet[inputMethodId];
  11096.           }
  11097.         }
  11098.       }
  11099.  
  11100.       return newPreloadEngines;
  11101.     },
  11102.  
  11103.     /**
  11104.      * Initializes the map of language code to input method IDs.
  11105.      * @private
  11106.      */
  11107.     initializeLanguageCodeToInputMethodIdsMap_: function() {
  11108.       var inputMethodList = loadTimeData.getValue('inputMethodList');
  11109.       for (var i = 0; i < inputMethodList.length; i++) {
  11110.         var inputMethod = inputMethodList[i];
  11111.         for (var languageCode in inputMethod.languageCodeSet) {
  11112.           if (languageCode in this.languageCodeToInputMethodIdsMap_) {
  11113.             this.languageCodeToInputMethodIdsMap_[languageCode].push(
  11114.                 inputMethod.id);
  11115.           } else {
  11116.             this.languageCodeToInputMethodIdsMap_[languageCode] =
  11117.                 [inputMethod.id];
  11118.           }
  11119.         }
  11120.       }
  11121.     },
  11122.  
  11123.     /**
  11124.      * Updates the currently selected language name.
  11125.      * @param {string} languageCode Language code (ex. "fr").
  11126.      * @private
  11127.      */
  11128.     updateSelectedLanguageName_: function(languageCode) {
  11129.       var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
  11130.           languageCode);
  11131.       var languageDisplayName = languageInfo.displayName;
  11132.       var languageNativeDisplayName = languageInfo.nativeDisplayName;
  11133.       var textDirection = languageInfo.textDirection;
  11134.  
  11135.       // If the native name is different, add it.
  11136.       if (languageDisplayName != languageNativeDisplayName) {
  11137.         languageDisplayName += ' - ' + languageNativeDisplayName;
  11138.       }
  11139.  
  11140.       // Update the currently selected language name.
  11141.       var languageName = $('language-options-language-name');
  11142.       languageName.textContent = languageDisplayName;
  11143.       languageName.dir = textDirection;
  11144.     },
  11145.  
  11146.     /**
  11147.      * Updates the UI language button.
  11148.      * @param {string} languageCode Language code (ex. "fr").
  11149.      * @private
  11150.      */
  11151.     updateUiLanguageButton_: function(languageCode) {
  11152.       var uiLanguageButton = $('language-options-ui-language-button');
  11153.       var uiLanguageMessage = $('language-options-ui-language-message');
  11154.       var uiLanguageNotification = $('language-options-ui-notification-bar');
  11155.  
  11156.       // Remove the event listener and add it back if useful.
  11157.       uiLanguageButton.onclick = null;
  11158.  
  11159.       // Unhide the language button every time, as it could've been previously
  11160.       // hidden by a language change.
  11161.       uiLanguageButton.hidden = false;
  11162.  
  11163.       if (languageCode == this.prospectiveUiLanguageCode_) {
  11164.         uiLanguageMessage.textContent =
  11165.             loadTimeData.getString('is_displayed_in_this_language');
  11166.         showMutuallyExclusiveNodes(
  11167.             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
  11168.       } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
  11169.         if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
  11170.           // In the guest mode for ChromeOS, changing UI language does not make
  11171.           // sense because it does not take effect after browser restart.
  11172.           uiLanguageButton.hidden = true;
  11173.           uiLanguageMessage.hidden = true;
  11174.         } else {
  11175.           uiLanguageButton.textContent =
  11176.               loadTimeData.getString('display_in_this_language');
  11177.           showMutuallyExclusiveNodes(
  11178.               [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
  11179.           uiLanguageButton.onclick = function(e) {
  11180.             chrome.send('uiLanguageChange', [languageCode]);
  11181.           };
  11182.         }
  11183.       } else {
  11184.         uiLanguageMessage.textContent =
  11185.             loadTimeData.getString('cannot_be_displayed_in_this_language');
  11186.         showMutuallyExclusiveNodes(
  11187.             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
  11188.       }
  11189.     },
  11190.  
  11191.     /**
  11192.      * Updates the spell check language button.
  11193.      * @param {string} languageCode Language code (ex. "fr").
  11194.      * @private
  11195.      */
  11196.     updateSpellCheckLanguageButton_: function(languageCode) {
  11197.       var spellCheckLanguageButton =
  11198.           $('language-options-spell-check-language-button');
  11199.       var spellCheckLanguageMessage =
  11200.           $('language-options-spell-check-language-message');
  11201.  
  11202.       if (languageCode == this.spellCheckDictionary_) {
  11203.         spellCheckLanguageMessage.textContent =
  11204.             loadTimeData.getString('is_used_for_spell_checking');
  11205.         showMutuallyExclusiveNodes(
  11206.             [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
  11207.       } else if (languageCode in
  11208.           loadTimeData.getValue('spellCheckLanguageCodeSet')) {
  11209.         spellCheckLanguageButton.textContent =
  11210.             loadTimeData.getString('use_this_for_spell_checking');
  11211.         showMutuallyExclusiveNodes(
  11212.             [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
  11213.         spellCheckLanguageButton.languageCode = languageCode;
  11214.       } else if (!languageCode) {
  11215.         spellCheckLanguageButton.hidden = true;
  11216.         spellCheckLanguageMessage.hidden = true;
  11217.       } else {
  11218.         spellCheckLanguageMessage.textContent =
  11219.             loadTimeData.getString('cannot_be_used_for_spell_checking');
  11220.         showMutuallyExclusiveNodes(
  11221.             [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
  11222.       }
  11223.     },
  11224.  
  11225.     /**
  11226.      * Updates the input method list.
  11227.      * @param {string} languageCode Language code (ex. "fr").
  11228.      * @private
  11229.      */
  11230.     updateInputMethodList_: function(languageCode) {
  11231.       // Give one of the checkboxes or buttons focus, if it's specified in the
  11232.       // URL hash (ex. focus=mozc). Used for automated testing.
  11233.       var focusInputMethodId = -1;
  11234.       var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
  11235.       if (match) {
  11236.         focusInputMethodId = match[1];
  11237.       }
  11238.       // Change the visibility of the input method list. Input methods that
  11239.       // matches |languageCode| will become visible.
  11240.       var inputMethodList = $('language-options-input-method-list');
  11241.       var methods = inputMethodList.querySelectorAll('.input-method');
  11242.       for (var i = 0; i < methods.length; i++) {
  11243.         var method = methods[i];
  11244.         if (languageCode in method.languageCodeSet) {
  11245.           method.hidden = false;
  11246.           var input = method.querySelectorAll('input')[0];
  11247.           // Give it focus if the ID matches.
  11248.           if (input.inputMethodId == focusInputMethodId) {
  11249.             input.focus();
  11250.           }
  11251.         } else {
  11252.           method.hidden = true;
  11253.         }
  11254.       }
  11255.  
  11256.       if (focusInputMethodId == 'add') {
  11257.         $('language-options-add-button').focus();
  11258.       }
  11259.     },
  11260.  
  11261.     /**
  11262.      * Updates the language list in the add language overlay.
  11263.      * @param {string} languageCode Language code (ex. "fr").
  11264.      * @private
  11265.      */
  11266.     updateLanguageListInAddLanguageOverlay_: function(languageCode) {
  11267.       // Change the visibility of the language list in the add language
  11268.       // overlay. Languages that are already active will become invisible,
  11269.       // so that users don't add the same language twice.
  11270.       var languageOptionsList = $('language-options-list');
  11271.       var languageCodes = languageOptionsList.getLanguageCodes();
  11272.       var languageCodeSet = {};
  11273.       for (var i = 0; i < languageCodes.length; i++) {
  11274.         languageCodeSet[languageCodes[i]] = true;
  11275.       }
  11276.       var addLanguageList = $('add-language-overlay-language-list');
  11277.       var lis = addLanguageList.querySelectorAll('li');
  11278.       for (var i = 0; i < lis.length; i++) {
  11279.         // The first child button knows the language code.
  11280.         var button = lis[i].childNodes[0];
  11281.         if (button.languageCode in languageCodeSet) {
  11282.           lis[i].style.display = 'none';
  11283.         } else {
  11284.           lis[i].style.display = 'block';
  11285.         }
  11286.       }
  11287.     },
  11288.  
  11289.     /**
  11290.      * Handles preloadEnginesPref change.
  11291.      * @param {Event} e Change event.
  11292.      * @private
  11293.      */
  11294.     handlePreloadEnginesPrefChange_: function(e) {
  11295.       var value = e.value.value;
  11296.       this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
  11297.       this.updateCheckboxesFromPreloadEngines_();
  11298.       $('language-options-list').updateDeletable();
  11299.     },
  11300.  
  11301.     /**
  11302.      * Handles filteredExtensionImesPref change.
  11303.      * @param {Event} e Change event.
  11304.      * @private
  11305.      */
  11306.     handleFilteredExtensionsPrefChange_: function(e) {
  11307.       var value = e.value.value;
  11308.       this.filteredExtensionImes_ = value.split(',');
  11309.       this.updateCheckboxesFromFilteredExtensions_();
  11310.     },
  11311.  
  11312.     /**
  11313.      * Handles input method checkbox's click event.
  11314.      * @param {Event} e Click event.
  11315.      * @private
  11316.      */
  11317.     handleCheckboxClick_: function(e) {
  11318.       var checkbox = e.target;
  11319.       if (this.preloadEngines_.length == 1 && !checkbox.checked) {
  11320.         // Don't allow disabling the last input method.
  11321.         this.showNotification_(
  11322.             loadTimeData.getString('please_add_another_input_method'),
  11323.             loadTimeData.getString('ok_button'));
  11324.         checkbox.checked = true;
  11325.         return;
  11326.       }
  11327.       if (checkbox.checked) {
  11328.         chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
  11329.       } else {
  11330.         chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
  11331.       }
  11332.       this.updatePreloadEnginesFromCheckboxes_();
  11333.       this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
  11334.       this.savePreloadEnginesPref_();
  11335.     },
  11336.  
  11337.     /**
  11338.      * Handles extension input method checkbox's click event.
  11339.      * @param {Event} e Click event.
  11340.      * @private
  11341.      */
  11342.     handleExtensionCheckboxClick_: function(e) {
  11343.       var checkbox = e.target;
  11344.       this.updateFilteredExtensionsFromCheckboxes_();
  11345.       this.saveFilteredExtensionPref_();
  11346.     },
  11347.  
  11348.     /**
  11349.      * Handles add language list's click event.
  11350.      * @param {Event} e Click event.
  11351.      */
  11352.     handleAddLanguageListClick_: function(e) {
  11353.       var languageOptionsList = $('language-options-list');
  11354.       var languageCode = e.target.languageCode;
  11355.       // languageCode can be undefined, if click was made on some random
  11356.       // place in the overlay, rather than a button. Ignore it.
  11357.       if (!languageCode) {
  11358.         return;
  11359.       }
  11360.       languageOptionsList.addLanguage(languageCode);
  11361.       var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
  11362.       // Enable the first input method for the language added.
  11363.       if (inputMethodIds && inputMethodIds[0] &&
  11364.           // Don't add the input method it's already present. This can
  11365.           // happen if the same input method is shared among multiple
  11366.           // languages (ex. English US keyboard is used for English US and
  11367.           // Filipino).
  11368.           this.preloadEngines_.indexOf(inputMethodIds[0]) == -1) {
  11369.         this.preloadEngines_.push(inputMethodIds[0]);
  11370.         this.updateCheckboxesFromPreloadEngines_();
  11371.         this.savePreloadEnginesPref_();
  11372.       }
  11373.       OptionsPage.closeOverlay();
  11374.     },
  11375.  
  11376.     /**
  11377.      * Handles extension IME button.
  11378.      */
  11379.     handleExtensionImeButtonClick_: function() {
  11380.       $('language-options-list').clearSelection();
  11381.  
  11382.       var languageName = $('language-options-language-name');
  11383.       languageName.textContent = loadTimeData.getString('extension_ime_label');
  11384.  
  11385.       var uiLanguageMessage = $('language-options-ui-language-message');
  11386.       uiLanguageMessage.textContent =
  11387.           loadTimeData.getString('extension_ime_description');
  11388.  
  11389.       var uiLanguageButton = $('language-options-ui-language-button');
  11390.       uiLanguageButton.onclick = null;
  11391.       uiLanguageButton.hidden = true;
  11392.  
  11393.       this.updateSpellCheckLanguageButton_();
  11394.  
  11395.       // Hide all input method checkboxes that aren't extension IMEs.
  11396.       var inputMethodList = $('language-options-input-method-list');
  11397.       var methods = inputMethodList.querySelectorAll('.input-method');
  11398.       for (var i = 0; i < methods.length; i++) {
  11399.         var method = methods[i];
  11400.         var input = method.querySelectorAll('input')[0];
  11401.         // Give it focus if the ID matches.
  11402.         if (input.inputMethodId.match(/^_ext_ime_/))
  11403.           method.hidden = false;
  11404.         else
  11405.           method.hidden = true;
  11406.       }
  11407.     },
  11408.  
  11409.  
  11410.     handleAddLanguageOkButtonClick_: function() {
  11411.       var languagesSelect = $('add-language-overlay-language-list');
  11412.       var selectedIndex = languagesSelect.selectedIndex;
  11413.       if (selectedIndex >= 0) {
  11414.         var selection = languagesSelect.options[selectedIndex];
  11415.         $('language-options-list').addLanguage(String(selection.value));
  11416.         OptionsPage.closeOverlay();
  11417.       }
  11418.     },
  11419.  
  11420.     /**
  11421.      * Checks if languageCode is deletable or not.
  11422.      * @param {String} languageCode the languageCode to check for deletability.
  11423.      */
  11424.     languageIsDeletable: function(languageCode) {
  11425.       // Don't allow removing the language if it's a UI language.
  11426.       if (languageCode == this.prospectiveUiLanguageCode_)
  11427.         return false;
  11428.       return (!cr.isChromeOS ||
  11429.               this.canDeleteLanguage_(languageCode));
  11430.     },
  11431.  
  11432.     /**
  11433.      * Handles browse.enable_spellchecking change.
  11434.      * @param {Event} e Change event.
  11435.      * @private
  11436.      */
  11437.      updateEnableSpellCheck_: function() {
  11438.        var value = !$('enable-spell-check').checked;
  11439.        $('language-options-spell-check-language-button').disabled = value;
  11440.        if (!cr.IsMac)
  11441.          $('edit-dictionary-button').hidden = value;
  11442.      },
  11443.  
  11444.     /**
  11445.      * Handles spellCheckDictionaryPref change.
  11446.      * @param {Event} e Change event.
  11447.      * @private
  11448.      */
  11449.     handleSpellCheckDictionaryPrefChange_: function(e) {
  11450.       var languageCode = e.value.value;
  11451.       this.spellCheckDictionary_ = languageCode;
  11452.       var languageOptionsList = $('language-options-list');
  11453.       var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
  11454.       if (!cr.isMac)
  11455.         this.updateSpellCheckLanguageButton_(selectedLanguageCode);
  11456.     },
  11457.  
  11458.     /**
  11459.      * Handles spellCheckLanguageButton click.
  11460.      * @param {Event} e Click event.
  11461.      * @private
  11462.      */
  11463.     handleSpellCheckLanguageButtonClick_: function(e) {
  11464.       var languageCode = e.target.languageCode;
  11465.       // Save the preference.
  11466.       Preferences.setStringPref(this.spellCheckDictionaryPref,
  11467.                                 languageCode, true);
  11468.       chrome.send('spellCheckLanguageChange', [languageCode]);
  11469.     },
  11470.  
  11471.     /**
  11472.      * Checks whether it's possible to remove the language specified by
  11473.      * languageCode and returns true if possible. This function returns false
  11474.      * if the removal causes the number of preload engines to be zero.
  11475.      *
  11476.      * @param {string} languageCode Language code (ex. "fr").
  11477.      * @return {boolean} Returns true on success.
  11478.      * @private
  11479.      */
  11480.     canDeleteLanguage_: function(languageCode) {
  11481.       // First create the set of engines to be removed from input methods
  11482.       // associated with the language code.
  11483.       var enginesToBeRemovedSet = {};
  11484.       var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
  11485.       for (var i = 0; i < inputMethodIds.length; i++) {
  11486.         enginesToBeRemovedSet[inputMethodIds[i]] = true;
  11487.       }
  11488.  
  11489.       // Then eliminate engines that are also used for other active languages.
  11490.       // For instance, if "xkb:us::eng" is used for both English and Filipino.
  11491.       var languageCodes = $('language-options-list').getLanguageCodes();
  11492.       for (var i = 0; i < languageCodes.length; i++) {
  11493.         // Skip the target language code.
  11494.         if (languageCodes[i] == languageCode) {
  11495.           continue;
  11496.         }
  11497.         // Check if input methods used in this language are included in
  11498.         // enginesToBeRemovedSet. If so, eliminate these from the set, so
  11499.         // we don't remove this time.
  11500.         var inputMethodIdsForAnotherLanguage =
  11501.             this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
  11502.         for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
  11503.           var inputMethodId = inputMethodIdsForAnotherLanguage[j];
  11504.           if (inputMethodId in enginesToBeRemovedSet) {
  11505.             delete enginesToBeRemovedSet[inputMethodId];
  11506.           }
  11507.         }
  11508.       }
  11509.  
  11510.       // Update the preload engine list with the to-be-removed set.
  11511.       var newPreloadEngines = [];
  11512.       for (var i = 0; i < this.preloadEngines_.length; i++) {
  11513.         if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
  11514.           newPreloadEngines.push(this.preloadEngines_[i]);
  11515.         }
  11516.       }
  11517.       // Don't allow this operation if it causes the number of preload
  11518.       // engines to be zero.
  11519.       return (newPreloadEngines.length > 0);
  11520.     },
  11521.  
  11522.     /**
  11523.      * Saves the filtered extension preference.
  11524.      * @private
  11525.      */
  11526.     saveFilteredExtensionPref_: function() {
  11527.       Preferences.setStringPref(this.filteredExtensionImesPref,
  11528.                                 this.filteredExtensionImes_.join(','), true);
  11529.     },
  11530.  
  11531.     /**
  11532.      * Updates the checkboxes in the input method list from the filtered
  11533.      * extensions preference.
  11534.      * @private
  11535.      */
  11536.     updateCheckboxesFromFilteredExtensions_: function() {
  11537.       // Convert the list into a dictonary for simpler lookup.
  11538.       var dictionary = {};
  11539.       for (var i = 0; i < this.filteredExtensionImes_.length; i++)
  11540.         dictionary[this.filteredExtensionImes_[i]] = true;
  11541.  
  11542.       var inputMethodList = $('language-options-input-method-list');
  11543.       var checkboxes = inputMethodList.querySelectorAll('input');
  11544.       for (var i = 0; i < checkboxes.length; i++) {
  11545.         if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
  11546.           checkboxes[i].checked = !(checkboxes[i].inputMethodId in dictionary);
  11547.       }
  11548.     },
  11549.  
  11550.     /**
  11551.      * Updates the filtered extensions preference from the checkboxes in the
  11552.      * input method list.
  11553.      * @private
  11554.      */
  11555.     updateFilteredExtensionsFromCheckboxes_: function() {
  11556.       this.filteredExtensionImes_ = [];
  11557.       var inputMethodList = $('language-options-input-method-list');
  11558.       var checkboxes = inputMethodList.querySelectorAll('input');
  11559.       for (var i = 0; i < checkboxes.length; i++) {
  11560.         if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
  11561.           if (!checkboxes[i].checked)
  11562.             this.filteredExtensionImes_.push(checkboxes[i].inputMethodId);
  11563.         }
  11564.       }
  11565.     },
  11566.  
  11567.     /**
  11568.      * Saves the preload engines preference.
  11569.      * @private
  11570.      */
  11571.     savePreloadEnginesPref_: function() {
  11572.       Preferences.setStringPref(this.preloadEnginesPref,
  11573.                                 this.preloadEngines_.join(','), true);
  11574.     },
  11575.  
  11576.     /**
  11577.      * Updates the checkboxes in the input method list from the preload
  11578.      * engines preference.
  11579.      * @private
  11580.      */
  11581.     updateCheckboxesFromPreloadEngines_: function() {
  11582.       // Convert the list into a dictonary for simpler lookup.
  11583.       var dictionary = {};
  11584.       for (var i = 0; i < this.preloadEngines_.length; i++) {
  11585.         dictionary[this.preloadEngines_[i]] = true;
  11586.       }
  11587.  
  11588.       var inputMethodList = $('language-options-input-method-list');
  11589.       var checkboxes = inputMethodList.querySelectorAll('input');
  11590.       for (var i = 0; i < checkboxes.length; i++) {
  11591.         if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
  11592.           checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
  11593.       }
  11594.     },
  11595.  
  11596.     /**
  11597.      * Updates the preload engines preference from the checkboxes in the
  11598.      * input method list.
  11599.      * @private
  11600.      */
  11601.     updatePreloadEnginesFromCheckboxes_: function() {
  11602.       this.preloadEngines_ = [];
  11603.       var inputMethodList = $('language-options-input-method-list');
  11604.       var checkboxes = inputMethodList.querySelectorAll('input');
  11605.       for (var i = 0; i < checkboxes.length; i++) {
  11606.         if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
  11607.           if (checkboxes[i].checked)
  11608.             this.preloadEngines_.push(checkboxes[i].inputMethodId);
  11609.         }
  11610.       }
  11611.       var languageOptionsList = $('language-options-list');
  11612.       languageOptionsList.updateDeletable();
  11613.     },
  11614.  
  11615.     /**
  11616.      * Filters bad preload engines in case bad preload engines are
  11617.      * stored in the preference. Removes duplicates as well.
  11618.      * @param {Array} preloadEngines List of preload engines.
  11619.      * @private
  11620.      */
  11621.     filterBadPreloadEngines_: function(preloadEngines) {
  11622.       // Convert the list into a dictonary for simpler lookup.
  11623.       var dictionary = {};
  11624.       var list = loadTimeData.getValue('inputMethodList');
  11625.       for (var i = 0; i < list.length; i++) {
  11626.         dictionary[list[i].id] = true;
  11627.       }
  11628.  
  11629.       var filteredPreloadEngines = [];
  11630.       var seen = {};
  11631.       for (var i = 0; i < preloadEngines.length; i++) {
  11632.         // Check if the preload engine is present in the
  11633.         // dictionary, and not duplicate. Otherwise, skip it.
  11634.         if (preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) {
  11635.           filteredPreloadEngines.push(preloadEngines[i]);
  11636.           seen[preloadEngines[i]] = true;
  11637.         }
  11638.       }
  11639.       return filteredPreloadEngines;
  11640.     },
  11641.  
  11642.     // TODO(kochi): This is an adapted copy from new_tab.js.
  11643.     // If this will go as final UI, refactor this to share the component with
  11644.     // new new tab page.
  11645.     /**
  11646.      * Shows notification
  11647.      * @private
  11648.      */
  11649.     notificationTimeout_: null,
  11650.     showNotification_: function(text, actionText, opt_delay) {
  11651.       var notificationElement = $('notification');
  11652.       var actionLink = notificationElement.querySelector('.link-color');
  11653.       var delay = opt_delay || 10000;
  11654.  
  11655.       function show() {
  11656.         window.clearTimeout(this.notificationTimeout_);
  11657.         notificationElement.classList.add('show');
  11658.         document.body.classList.add('notification-shown');
  11659.       }
  11660.  
  11661.       function hide() {
  11662.         window.clearTimeout(this.notificationTimeout_);
  11663.         notificationElement.classList.remove('show');
  11664.         document.body.classList.remove('notification-shown');
  11665.         // Prevent tabbing to the hidden link.
  11666.         actionLink.tabIndex = -1;
  11667.         // Setting tabIndex to -1 only prevents future tabbing to it. If,
  11668.         // however, the user switches window or a tab and then moves back to
  11669.         // this tab the element may gain focus. We therefore make sure that we
  11670.         // blur the element so that the element focus is not restored when
  11671.         // coming back to this window.
  11672.         actionLink.blur();
  11673.       }
  11674.  
  11675.       function delayedHide() {
  11676.         this.notificationTimeout_ = window.setTimeout(hide, delay);
  11677.       }
  11678.  
  11679.       notificationElement.firstElementChild.textContent = text;
  11680.       actionLink.textContent = actionText;
  11681.  
  11682.       actionLink.onclick = hide;
  11683.       actionLink.onkeydown = function(e) {
  11684.         if (e.keyIdentifier == 'Enter') {
  11685.           hide();
  11686.         }
  11687.       };
  11688.       notificationElement.onmouseover = show;
  11689.       notificationElement.onmouseout = delayedHide;
  11690.       actionLink.onfocus = show;
  11691.       actionLink.onblur = delayedHide;
  11692.       // Enable tabbing to the link now that it is shown.
  11693.       actionLink.tabIndex = 0;
  11694.  
  11695.       show();
  11696.       delayedHide();
  11697.     }
  11698.   };
  11699.  
  11700.   /**
  11701.    * Shows the node at |index| in |nodes|, hides all others.
  11702.    * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
  11703.    * @param {number} index The index of |nodes| to show.
  11704.    */
  11705.   function showMutuallyExclusiveNodes(nodes, index) {
  11706.     assert(index >= 0 && index < nodes.length);
  11707.     for (var i = 0; i < nodes.length; ++i) {
  11708.       assert(nodes[i] instanceof HTMLElement);  // TODO(dbeam): Ignore null?
  11709.       nodes[i].hidden = i != index;
  11710.     }
  11711.   }
  11712.  
  11713.   /**
  11714.    * Chrome callback for when the UI language preference is saved.
  11715.    * @param {string} languageCode The newly selected language to use.
  11716.    */
  11717.   LanguageOptions.uiLanguageSaved = function(languageCode) {
  11718.     this.prospectiveUiLanguageCode_ = languageCode;
  11719.  
  11720.     // If the user is no longer on the same language code, ignore.
  11721.     if ($('language-options-list').getSelectedLanguageCode() != languageCode)
  11722.       return;
  11723.  
  11724.     // Special case for when a user changes to a different language, and changes
  11725.     // back to the same language without having restarted Chrome or logged
  11726.     // in/out of ChromeOS.
  11727.     if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
  11728.       LanguageOptions.getInstance().currentLocaleWasReselected();
  11729.       return;
  11730.     }
  11731.  
  11732.     // Otherwise, show a notification telling the user that their changes will
  11733.     // only take effect after restart.
  11734.     showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
  11735.                                 $('language-options-ui-notification-bar')], 1);
  11736.   };
  11737.  
  11738.   // Export
  11739.   return {
  11740.     LanguageOptions: LanguageOptions
  11741.   };
  11742. });
  11743.  
  11744. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  11745. // Use of this source code is governed by a BSD-style license that can be
  11746. // found in the LICENSE file.
  11747.  
  11748. cr.define('options', function() {
  11749.   var OptionsPage = options.OptionsPage;
  11750.   var ArrayDataModel = cr.ui.ArrayDataModel;
  11751.  
  11752.   /**
  11753.    * ManageProfileOverlay class
  11754.    * Encapsulated handling of the 'Manage profile...' overlay page.
  11755.    * @constructor
  11756.    * @class
  11757.    */
  11758.   function ManageProfileOverlay() {
  11759.     OptionsPage.call(this, 'manageProfile',
  11760.                      loadTimeData.getString('manageProfileTabTitle'),
  11761.                      'manage-profile-overlay');
  11762.   };
  11763.  
  11764.   cr.addSingletonGetter(ManageProfileOverlay);
  11765.  
  11766.   ManageProfileOverlay.prototype = {
  11767.     // Inherit from OptionsPage.
  11768.     __proto__: OptionsPage.prototype,
  11769.  
  11770.     // Info about the currently managed/deleted profile.
  11771.     profileInfo_: null,
  11772.  
  11773.     // An object containing all known profile names.
  11774.     profileNames_: {},
  11775.  
  11776.     // The currently selected icon in the icon grid.
  11777.     iconGridSelectedURL_: null,
  11778.  
  11779.     /**
  11780.      * Initialize the page.
  11781.      */
  11782.     initializePage: function() {
  11783.       // Call base class implementation to start preference initialization.
  11784.       OptionsPage.prototype.initializePage.call(this);
  11785.  
  11786.       var self = this;
  11787.       var iconGrid = $('manage-profile-icon-grid');
  11788.       var createIconGrid = $('create-profile-icon-grid');
  11789.       options.ProfilesIconGrid.decorate(iconGrid);
  11790.       options.ProfilesIconGrid.decorate(createIconGrid);
  11791.       iconGrid.addEventListener('change', function(e) {
  11792.         self.onIconGridSelectionChanged_('manage');
  11793.       });
  11794.       createIconGrid.addEventListener('change', function(e) {
  11795.         self.onIconGridSelectionChanged_('create');
  11796.       });
  11797.  
  11798.       $('manage-profile-name').oninput = function(event) {
  11799.         self.onNameChanged_(event, 'manage');
  11800.       };
  11801.       $('create-profile-name').oninput = function(event) {
  11802.         self.onNameChanged_(event, 'create');
  11803.       };
  11804.       $('manage-profile-cancel').onclick =
  11805.           $('delete-profile-cancel').onclick =
  11806.               $('create-profile-cancel').onclick = function(event) {
  11807.         OptionsPage.closeOverlay();
  11808.       };
  11809.       $('manage-profile-ok').onclick = function(event) {
  11810.         OptionsPage.closeOverlay();
  11811.         self.submitManageChanges_();
  11812.       };
  11813.       $('delete-profile-ok').onclick = function(event) {
  11814.         OptionsPage.closeOverlay();
  11815.         chrome.send('deleteProfile', [self.profileInfo_.filePath]);
  11816.       };
  11817.       $('create-profile-ok').onclick = function(event) {
  11818.         OptionsPage.closeOverlay();
  11819.         // Get the user's chosen name and icon, or default if they do not
  11820.         // wish to customize their profile.
  11821.         var name = $('create-profile-name').value;
  11822.         var icon_url = createIconGrid.selectedItem;
  11823.         var create_checkbox = false;
  11824.         if ($('create-shortcut'))
  11825.           create_checkbox = $('create-shortcut').checked;
  11826.         chrome.send('createProfile', [name, icon_url, create_checkbox]);
  11827.       };
  11828.     },
  11829.  
  11830.     /** @override */
  11831.     didShowPage: function() {
  11832.       chrome.send('requestDefaultProfileIcons');
  11833.  
  11834.       if ($('create-shortcut'))
  11835.         $('create-shortcut').checked = true;
  11836.       if ($('manage-shortcut'))
  11837.         $('manage-shortcut').checked = false;
  11838.  
  11839.       // Just ignore the manage profile dialog on Chrome OS, they use /accounts.
  11840.       if (!cr.isChromeOS && window.location.pathname == '/manageProfile')
  11841.         ManageProfileOverlay.getInstance().prepareForManageDialog_();
  11842.  
  11843.       $('manage-profile-name').focus();
  11844.       $('create-profile-name').focus();
  11845.     },
  11846.  
  11847.     /**
  11848.      * Set the profile info used in the dialog.
  11849.      * @param {Object} profileInfo An object of the form:
  11850.      *     profileInfo = {
  11851.      *       name: "Profile Name",
  11852.      *       iconURL: "chrome://path/to/icon/image",
  11853.      *       filePath: "/path/to/profile/data/on/disk"
  11854.      *       isCurrentProfile: false,
  11855.      *     };
  11856.      * @param {String} mode A label that specifies the type of dialog
  11857.      *     box which is currently being viewed (i.e. 'create' or
  11858.      *     'manage').
  11859.      * @private
  11860.      */
  11861.     setProfileInfo_: function(profileInfo, mode) {
  11862.       this.iconGridSelectedURL_ = profileInfo.iconURL;
  11863.       this.profileInfo_ = profileInfo;
  11864.       $(mode + '-profile-name').value = profileInfo.name;
  11865.       $(mode + '-profile-icon-grid').selectedItem = profileInfo.iconURL;
  11866.     },
  11867.  
  11868.     /**
  11869.      * Sets the name of the currently edited profile.
  11870.      * @private
  11871.      */
  11872.     setProfileName_: function(name) {
  11873.       if (this.profileInfo_)
  11874.         this.profileInfo_.name = name;
  11875.       $('manage-profile-name').value = name;
  11876.     },
  11877.  
  11878.     /**
  11879.      * the user will use to choose their profile icon.
  11880.      * @param {Array.<string>} iconURLs An array of icon URLs.
  11881.      * @private
  11882.      */
  11883.     receiveDefaultProfileIcons_: function(iconGrid, iconURLs) {
  11884.       $(iconGrid).dataModel = new ArrayDataModel(iconURLs);
  11885.  
  11886.       if (this.profileInfo_)
  11887.         $(iconGrid).selectedItem = this.profileInfo_.iconURL;
  11888.  
  11889.       var grid = $(iconGrid);
  11890.       // Recalculate the measured item size.
  11891.       grid.measured_ = null;
  11892.       grid.columns = 0;
  11893.       grid.redraw();
  11894.     },
  11895.  
  11896.     /**
  11897.      * Set a dictionary of all profile names. These are used to prevent the
  11898.      * user from naming two profiles the same.
  11899.      * @param {Object} profileNames A dictionary of profile names.
  11900.      * @private
  11901.      */
  11902.     receiveProfileNames_: function(profileNames) {
  11903.       this.profileNames_ = profileNames;
  11904.     },
  11905.  
  11906.     /**
  11907.      * Display the error bubble, with |errorText| in the bubble.
  11908.      * @param {string} errorText The localized string id to display as an error.
  11909.      * @param {String} mode A label that specifies the type of dialog
  11910.      *     box which is currently being viewed (i.e. 'create' or
  11911.      *     'manage').
  11912.      * @private
  11913.      */
  11914.     showErrorBubble_: function(errorText, mode) {
  11915.       var nameErrorEl = $(mode + '-profile-error-bubble');
  11916.       nameErrorEl.hidden = false;
  11917.       nameErrorEl.textContent = loadTimeData.getString(errorText);
  11918.  
  11919.       $(mode + '-profile-ok').disabled = true;
  11920.     },
  11921.  
  11922.     /**
  11923.      * Hide the error bubble.
  11924.      * @param {String} mode A label that specifies the type of dialog
  11925.      *     box which is currently being viewed (i.e. 'create' or
  11926.      *     'manage').
  11927.      * @private
  11928.      */
  11929.     hideErrorBubble_: function(mode) {
  11930.       $(mode + '-profile-error-bubble').hidden = true;
  11931.       $(mode + '-profile-ok').disabled = false;
  11932.     },
  11933.  
  11934.     /**
  11935.      * oninput callback for <input> field.
  11936.      * @param {Event} event The event object.
  11937.      * @param {String} mode A label that specifies the type of dialog
  11938.      *     box which is currently being viewed (i.e. 'create' or
  11939.      *     'manage').
  11940.      * @private
  11941.      */
  11942.     onNameChanged_: function(event, mode) {
  11943.       var newName = event.target.value;
  11944.       var oldName = this.profileInfo_.name;
  11945.  
  11946.       if (newName == oldName) {
  11947.         this.hideErrorBubble_(mode);
  11948.       } else if (this.profileNames_[newName] != undefined) {
  11949.         this.showErrorBubble_('manageProfilesDuplicateNameError', mode);
  11950.       } else {
  11951.         this.hideErrorBubble_(mode);
  11952.  
  11953.         var nameIsValid = $(mode + '-profile-name').validity.valid;
  11954.         $(mode + '-profile-ok').disabled = !nameIsValid;
  11955.       }
  11956.     },
  11957.  
  11958.     /**
  11959.      * Called when the user clicks "OK". Saves the newly changed profile info.
  11960.      * @private
  11961.      */
  11962.     submitManageChanges_: function() {
  11963.       var name = $('manage-profile-name').value;
  11964.       var iconURL = $('manage-profile-icon-grid').selectedItem;
  11965.       var manage_checkbox = false;
  11966.       if ($('manage-shortcut'))
  11967.         manage_checkbox = $('manage-shortcut').checked;
  11968.       chrome.send('setProfileNameAndIcon',
  11969.                   [this.profileInfo_.filePath, name, iconURL,
  11970.                   manage_checkbox]);
  11971.     },
  11972.  
  11973.     /**
  11974.      * Called when the selected icon in the icon grid changes.
  11975.      * @param {String} mode A label that specifies the type of dialog
  11976.      *     box which is currently being viewed (i.e. 'create' or
  11977.      *     'manage').
  11978.      * @private
  11979.      */
  11980.     onIconGridSelectionChanged_: function(mode) {
  11981.       var iconURL = $(mode + '-profile-icon-grid').selectedItem;
  11982.       if (!iconURL || iconURL == this.iconGridSelectedURL_)
  11983.         return;
  11984.       this.iconGridSelectedURL_ = iconURL;
  11985.       chrome.send('profileIconSelectionChanged',
  11986.                   [this.profileInfo_.filePath, iconURL]);
  11987.     },
  11988.  
  11989.     /**
  11990.      * Updates the contents of the "Manage Profile" section of the dialog,
  11991.      * and shows that section.
  11992.      * @private
  11993.      */
  11994.     prepareForManageDialog_: function() {
  11995.       var profileInfo = BrowserOptions.getCurrentProfile();
  11996.       ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
  11997.       $('manage-profile-overlay-create').hidden = true;
  11998.       $('manage-profile-overlay-manage').hidden = false;
  11999.       $('manage-profile-overlay-delete').hidden = true;
  12000.       this.hideErrorBubble_('manage');
  12001.     },
  12002.  
  12003.     /**
  12004.      * Display the "Manage Profile" dialog.
  12005.      * @private
  12006.      */
  12007.     showManageDialog_: function() {
  12008.       this.prepareForManageDialog_();
  12009.       OptionsPage.navigateToPage('manageProfile');
  12010.     },
  12011.  
  12012.     /**
  12013.      * Display the "Delete Profile" dialog.
  12014.      * @param {Object} profileInfo The profile object of the profile to delete.
  12015.      * @private
  12016.      */
  12017.     showDeleteDialog_: function(profileInfo) {
  12018.       ManageProfileOverlay.setProfileInfo(profileInfo, 'manage');
  12019.       $('manage-profile-overlay-create').hidden = true;
  12020.       $('manage-profile-overlay-manage').hidden = true;
  12021.       $('manage-profile-overlay-delete').hidden = false;
  12022.       $('delete-profile-message').textContent =
  12023.           loadTimeData.getStringF('deleteProfileMessage', profileInfo.name);
  12024.       $('delete-profile-message').style.backgroundImage = 'url("' +
  12025.           profileInfo.iconURL + '")';
  12026.  
  12027.       // Because this dialog isn't useful when refreshing or as part of the
  12028.       // history, don't create a history entry for it when showing.
  12029.       OptionsPage.showPageByName('manageProfile', false);
  12030.     },
  12031.  
  12032.     /**
  12033.      * Display the "Create Profile" dialog.
  12034.      * @param {Object} profileInfo The profile object of the profile to
  12035.      *     create. Upon creation, this object only needs a name and an avatar.
  12036.      * @private
  12037.      */
  12038.     showCreateDialog_: function(profileInfo) {
  12039.       ManageProfileOverlay.setProfileInfo(profileInfo, 'create');
  12040.       $('manage-profile-overlay-create').hidden = false;
  12041.       $('manage-profile-overlay-manage').hidden = true;
  12042.       $('manage-profile-overlay-delete').hidden = true;
  12043.       $('create-profile-instructions').textContent =
  12044.          loadTimeData.getStringF('createProfileInstructions');
  12045.       ManageProfileOverlay.getInstance().hideErrorBubble_('create');
  12046.  
  12047.       OptionsPage.showPageByName('manageProfile', false);
  12048.     },
  12049.  
  12050.   };
  12051.  
  12052.   // Forward public APIs to private implementations.
  12053.   [
  12054.     'receiveDefaultProfileIcons',
  12055.     'receiveProfileNames',
  12056.     'setProfileInfo',
  12057.     'setProfileName',
  12058.     'showManageDialog',
  12059.     'showDeleteDialog',
  12060.     'showCreateDialog',
  12061.   ].forEach(function(name) {
  12062.     ManageProfileOverlay[name] = function() {
  12063.       var instance = ManageProfileOverlay.getInstance();
  12064.       return instance[name + '_'].apply(instance, arguments);
  12065.     };
  12066.   });
  12067.  
  12068.   // Export
  12069.   return {
  12070.     ManageProfileOverlay: ManageProfileOverlay
  12071.   };
  12072. });
  12073.  
  12074. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12075. // Use of this source code is governed by a BSD-style license that can be
  12076. // found in the LICENSE file.
  12077.  
  12078. cr.define('options', function() {
  12079.   /** @const */ var DeletableItem = options.DeletableItem;
  12080.   /** @const */ var DeletableItemList = options.DeletableItemList;
  12081.  
  12082.   /**
  12083.    * @constructor
  12084.    * @extends {DeletableItem}
  12085.    */
  12086.   function MediaGalleriesListItem(galleryInfo) {
  12087.     var el = cr.doc.createElement('div');
  12088.     el.galleryInfo_ = galleryInfo;
  12089.     el.__proto__ = MediaGalleriesListItem.prototype;
  12090.     el.decorate();
  12091.     return el;
  12092.   }
  12093.  
  12094.   MediaGalleriesListItem.prototype = {
  12095.     __proto__: DeletableItem.prototype,
  12096.  
  12097.     decorate: function() {
  12098.       DeletableItem.prototype.decorate.call(this);
  12099.  
  12100.       var span = this.ownerDocument.createElement('span');
  12101.       span.textContent = this.galleryInfo_.displayName;
  12102.       this.contentElement.appendChild(span);
  12103.       this.contentElement.title = this.galleryInfo_.path;
  12104.     },
  12105.   };
  12106.  
  12107.   var MediaGalleriesList = cr.ui.define('list');
  12108.  
  12109.   MediaGalleriesList.prototype = {
  12110.     __proto__: DeletableItemList.prototype,
  12111.  
  12112.     /** @override */
  12113.     decorate: function() {
  12114.       DeletableItemList.prototype.decorate.call(this);
  12115.       this.autoExpands_ = true;
  12116.     },
  12117.  
  12118.     /** @override */
  12119.     createItem: function(galleryInfo) {
  12120.       return new MediaGalleriesListItem(galleryInfo);
  12121.     },
  12122.  
  12123.     /** @override */
  12124.     deleteItemAtIndex: function(index) {
  12125.       chrome.send('forgetGallery', [this.dataModel.item(index).id]);
  12126.     },
  12127.   };
  12128.  
  12129.   return {
  12130.     MediaGalleriesList: MediaGalleriesList
  12131.   };
  12132. });
  12133.  
  12134. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12135. // Use of this source code is governed by a BSD-style license that can be
  12136. // found in the LICENSE file.
  12137.  
  12138. cr.define('options', function() {
  12139.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  12140.   /** @const */ var OptionsPage = options.OptionsPage;
  12141.  
  12142.   /**
  12143.    * This class is an overlay which allows the user to add or remove media
  12144.    * galleries, and displays known media galleries.
  12145.    * @constructor
  12146.    * @extends {OptionsPage}
  12147.    */
  12148.   function MediaGalleriesManager() {
  12149.     OptionsPage.call(this, 'manageGalleries',
  12150.                      loadTimeData.getString('manageMediaGalleriesTabTitle'),
  12151.                      'manage-media-galleries-overlay');
  12152.   }
  12153.  
  12154.   cr.addSingletonGetter(MediaGalleriesManager);
  12155.  
  12156.   MediaGalleriesManager.prototype = {
  12157.     __proto__: OptionsPage.prototype,
  12158.  
  12159.     /**
  12160.      * Decorate the overlay and set up event handlers.
  12161.      */
  12162.     initializePage: function() {
  12163.       OptionsPage.prototype.initializePage.call(this);
  12164.  
  12165.       this.availableGalleriesList_ = $('available-galleries-list');
  12166.       options.MediaGalleriesList.decorate(this.availableGalleriesList_);
  12167.  
  12168.       $('new-media-gallery').addEventListener('click', function() {
  12169.         chrome.send('addNewGallery');
  12170.       });
  12171.  
  12172.       $('manage-media-confirm').addEventListener(
  12173.           'click', OptionsPage.closeOverlay.bind(OptionsPage));
  12174.  
  12175.       this.addEventListener('visibleChange', this.handleVisibleChange_);
  12176.     },
  12177.  
  12178.     /**
  12179.      * TODO(dbeam): why is a private method being overridden?
  12180.      * @override
  12181.      * @private
  12182.      */
  12183.     handleVisibleChange_: function() {
  12184.       if (!this.visible)
  12185.         return;
  12186.  
  12187.       if (this.availableGalleriesList_)
  12188.         this.availableGalleriesList_.redraw();
  12189.     },
  12190.  
  12191.     /**
  12192.      * @param {Array} galleries List of structs describibing galleries.
  12193.      * @private
  12194.      */
  12195.     setAvailableMediaGalleries_: function(galleries) {
  12196.       $('available-galleries-list').dataModel = new ArrayDataModel(galleries);
  12197.       // TODO(estade): show this section by default.
  12198.       $('media-galleries-section').hidden = false;
  12199.     },
  12200.   },
  12201.  
  12202.   // Forward public APIs to private implementations.
  12203.   [
  12204.     'setAvailableMediaGalleries',
  12205.   ].forEach(function(name) {
  12206.     MediaGalleriesManager[name] = function() {
  12207.       var instance = MediaGalleriesManager.getInstance();
  12208.       return instance[name + '_'].apply(instance, arguments);
  12209.     };
  12210.   });
  12211.  
  12212.   // Export
  12213.   return {
  12214.     MediaGalleriesManager: MediaGalleriesManager
  12215.   };
  12216. });
  12217.  
  12218. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12219. // Use of this source code is governed by a BSD-style license that can be
  12220. // found in the LICENSE file.
  12221.  
  12222. cr.define('options', function() {
  12223.   var FocusManager = cr.ui.FocusManager;
  12224.   var OptionsPage = options.OptionsPage;
  12225.  
  12226.   function OptionsFocusManager() {
  12227.   }
  12228.  
  12229.   cr.addSingletonGetter(OptionsFocusManager);
  12230.  
  12231.   OptionsFocusManager.prototype = {
  12232.     __proto__: FocusManager.prototype,
  12233.  
  12234.     /** @override */
  12235.     getFocusParent: function() {
  12236.       var topPage = OptionsPage.getTopmostVisiblePage().pageDiv;
  12237.  
  12238.       // The default page and search page include a search field that is a
  12239.       // sibling of the rest of the page instead of a child. Thus, use the
  12240.       // parent node to allow the search field to receive focus.
  12241.       if (topPage.parentNode.id == 'page-container')
  12242.         return topPage.parentNode;
  12243.  
  12244.       return topPage;
  12245.     },
  12246.   };
  12247.  
  12248.   return {
  12249.     OptionsFocusManager: OptionsFocusManager,
  12250.   };
  12251. });
  12252.  
  12253. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12254. // Use of this source code is governed by a BSD-style license that can be
  12255. // found in the LICENSE file.
  12256.  
  12257. cr.define('options', function() {
  12258.   /** @const */ var OptionsPage = options.OptionsPage;
  12259.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  12260.  
  12261.   /////////////////////////////////////////////////////////////////////////////
  12262.   // PasswordManager class:
  12263.  
  12264.   /**
  12265.    * Encapsulated handling of password and exceptions page.
  12266.    * @constructor
  12267.    */
  12268.   function PasswordManager() {
  12269.     this.activeNavTab = null;
  12270.     OptionsPage.call(this,
  12271.                      'passwords',
  12272.                      loadTimeData.getString('passwordsPageTabTitle'),
  12273.                      'password-manager');
  12274.   }
  12275.  
  12276.   cr.addSingletonGetter(PasswordManager);
  12277.  
  12278.   PasswordManager.prototype = {
  12279.     __proto__: OptionsPage.prototype,
  12280.  
  12281.     /**
  12282.      * The saved passwords list.
  12283.      * @type {DeletableItemList}
  12284.      * @private
  12285.      */
  12286.     savedPasswordsList_: null,
  12287.  
  12288.     /**
  12289.      * The password exceptions list.
  12290.      * @type {DeletableItemList}
  12291.      * @private
  12292.      */
  12293.     passwordExceptionsList_: null,
  12294.  
  12295.     /**
  12296.      * The timer id of the timer set on search query change events.
  12297.      * @type {number}
  12298.      * @private
  12299.      */
  12300.     queryDelayTimerId_: 0,
  12301.  
  12302.     /**
  12303.      * The most recent search query, or null if the query is empty.
  12304.      * @type {?string}
  12305.      * @private
  12306.      */
  12307.     lastQuery_: null,
  12308.  
  12309.     /** @override */
  12310.     initializePage: function() {
  12311.       OptionsPage.prototype.initializePage.call(this);
  12312.  
  12313.       $('password-manager-confirm').onclick = function() {
  12314.         OptionsPage.closeOverlay();
  12315.       };
  12316.  
  12317.       $('password-search-box').addEventListener('search',
  12318.           this.handleSearchQueryChange_.bind(this));
  12319.  
  12320.       this.createSavedPasswordsList_();
  12321.       this.createPasswordExceptionsList_();
  12322.     },
  12323.  
  12324.     /** @override */
  12325.     canShowPage: function() {
  12326.       return !(cr.isChromeOS && UIAccountTweaks.loggedInAsGuest());
  12327.     },
  12328.  
  12329.     /** @override */
  12330.     didShowPage: function() {
  12331.       // Updating the password lists may cause a blocking platform dialog pop up
  12332.       // (Mac, Linux), so we delay this operation until the page is shown.
  12333.       chrome.send('updatePasswordLists');
  12334.       $('password-search-box').focus();
  12335.     },
  12336.  
  12337.     /**
  12338.      * Creates, decorates and initializes the saved passwords list.
  12339.      * @private
  12340.      */
  12341.     createSavedPasswordsList_: function() {
  12342.       this.savedPasswordsList_ = $('saved-passwords-list');
  12343.       options.passwordManager.PasswordsList.decorate(this.savedPasswordsList_);
  12344.       this.savedPasswordsList_.autoExpands = true;
  12345.     },
  12346.  
  12347.     /**
  12348.      * Creates, decorates and initializes the password exceptions list.
  12349.      * @private
  12350.      */
  12351.     createPasswordExceptionsList_: function() {
  12352.       this.passwordExceptionsList_ = $('password-exceptions-list');
  12353.       options.passwordManager.PasswordExceptionsList.decorate(
  12354.           this.passwordExceptionsList_);
  12355.       this.passwordExceptionsList_.autoExpands = true;
  12356.     },
  12357.  
  12358.     /**
  12359.      * Handles search query changes.
  12360.      * @param {!Event} e The event object.
  12361.      * @private
  12362.      */
  12363.     handleSearchQueryChange_: function(e) {
  12364.       if (this.queryDelayTimerId_)
  12365.         window.clearTimeout(this.queryDelayTimerId_);
  12366.  
  12367.       // Searching cookies uses a timeout of 500ms. We use a shorter timeout
  12368.       // because there are probably fewer passwords and we want the UI to be
  12369.       // snappier since users will expect that it's "less work."
  12370.       this.queryDelayTimerId_ = window.setTimeout(
  12371.           this.searchPasswords_.bind(this), 250);
  12372.     },
  12373.  
  12374.     /**
  12375.      * Search passwords using text in |password-search-box|.
  12376.      * @private
  12377.      */
  12378.     searchPasswords_: function() {
  12379.       this.queryDelayTimerId_ = 0;
  12380.       var filter = $('password-search-box').value;
  12381.       filter = (filter == '') ? null : filter;
  12382.       if (this.lastQuery_ != filter) {
  12383.         this.lastQuery_ = filter;
  12384.         // Searching for passwords has the side effect of requerying the
  12385.         // underlying password store. This is done intentionally, as on OS X and
  12386.         // Linux they can change from outside and we won't be notified of it.
  12387.         chrome.send('updatePasswordLists');
  12388.       }
  12389.     },
  12390.  
  12391.     /**
  12392.      * Updates the visibility of the list and empty list placeholder.
  12393.      * @param {!List} list The list to toggle visilibility for.
  12394.      */
  12395.     updateListVisibility_: function(list) {
  12396.       var empty = list.dataModel.length == 0;
  12397.       var listPlaceHolderID = list.id + '-empty-placeholder';
  12398.       list.hidden = empty;
  12399.       $(listPlaceHolderID).hidden = !empty;
  12400.     },
  12401.  
  12402.     /**
  12403.      * Updates the data model for the saved passwords list with the values from
  12404.      * |entries|.
  12405.      * @param {Array} entries The list of saved password data.
  12406.      */
  12407.     setSavedPasswordsList_: function(entries) {
  12408.       if (this.lastQuery_) {
  12409.         // Implement password searching here in javascript, rather than in C++.
  12410.         // The number of saved passwords shouldn't be too big for us to handle.
  12411.         var query = this.lastQuery_;
  12412.         var filter = function(entry, index, list) {
  12413.           // Search both URL and username.
  12414.           if (entry[0].indexOf(query) >= 0 || entry[1].indexOf(query) >= 0) {
  12415.             // Keep the original index so we can delete correctly. See also
  12416.             // deleteItemAtIndex() in password_manager_list.js that uses this.
  12417.             entry[3] = index;
  12418.             return true;
  12419.           }
  12420.           return false;
  12421.         };
  12422.         entries = entries.filter(filter);
  12423.       }
  12424.       this.savedPasswordsList_.dataModel = new ArrayDataModel(entries);
  12425.       this.updateListVisibility_(this.savedPasswordsList_);
  12426.     },
  12427.  
  12428.     /**
  12429.      * Updates the data model for the password exceptions list with the values
  12430.      * from |entries|.
  12431.      * @param {Array} entries The list of password exception data.
  12432.      */
  12433.     setPasswordExceptionsList_: function(entries) {
  12434.       this.passwordExceptionsList_.dataModel = new ArrayDataModel(entries);
  12435.       this.updateListVisibility_(this.passwordExceptionsList_);
  12436.     },
  12437.   };
  12438.  
  12439.   /**
  12440.    * Removes a saved password.
  12441.    * @param {number} rowIndex indicating the row to remove.
  12442.    */
  12443.   PasswordManager.removeSavedPassword = function(rowIndex) {
  12444.       chrome.send('removeSavedPassword', [String(rowIndex)]);
  12445.   };
  12446.  
  12447.   /**
  12448.    * Removes a password exception.
  12449.    * @param {number} rowIndex indicating the row to remove.
  12450.    */
  12451.   PasswordManager.removePasswordException = function(rowIndex) {
  12452.       chrome.send('removePasswordException', [String(rowIndex)]);
  12453.   };
  12454.  
  12455.   /**
  12456.    * Removes all saved passwords.
  12457.    */
  12458.   PasswordManager.removeAllPasswords = function() {
  12459.     chrome.send('removeAllSavedPasswords');
  12460.   };
  12461.  
  12462.   /**
  12463.    * Removes all password exceptions.
  12464.    */
  12465.   PasswordManager.removeAllPasswordExceptions = function() {
  12466.     chrome.send('removeAllPasswordExceptions');
  12467.   };
  12468.  
  12469.   PasswordManager.setSavedPasswordsList = function(entries) {
  12470.     PasswordManager.getInstance().setSavedPasswordsList_(entries);
  12471.   };
  12472.  
  12473.   PasswordManager.setPasswordExceptionsList = function(entries) {
  12474.     PasswordManager.getInstance().setPasswordExceptionsList_(entries);
  12475.   };
  12476.  
  12477.   // Export
  12478.   return {
  12479.     PasswordManager: PasswordManager
  12480.   };
  12481.  
  12482. });
  12483.  
  12484. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12485. // Use of this source code is governed by a BSD-style license that can be
  12486. // found in the LICENSE file.
  12487.  
  12488. cr.define('options.passwordManager', function() {
  12489.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  12490.   /** @const */ var DeletableItemList = options.DeletableItemList;
  12491.   /** @const */ var DeletableItem = options.DeletableItem;
  12492.   /** @const */ var List = cr.ui.List;
  12493.  
  12494.   /**
  12495.    * Creates a new passwords list item.
  12496.    * @param {Array} entry An array of the form [url, username, password]. When
  12497.    *     the list has been filtered, a fourth element [index] may be present.
  12498.    * @constructor
  12499.    * @extends {cr.ui.ListItem}
  12500.    */
  12501.   function PasswordListItem(entry, showPasswords) {
  12502.     var el = cr.doc.createElement('div');
  12503.     el.dataItem = entry;
  12504.     el.__proto__ = PasswordListItem.prototype;
  12505.     el.decorate(showPasswords);
  12506.  
  12507.     return el;
  12508.   }
  12509.  
  12510.   PasswordListItem.prototype = {
  12511.     __proto__: DeletableItem.prototype,
  12512.  
  12513.     /** @override */
  12514.     decorate: function(showPasswords) {
  12515.       DeletableItem.prototype.decorate.call(this);
  12516.  
  12517.       // The URL of the site.
  12518.       var urlLabel = this.ownerDocument.createElement('div');
  12519.       urlLabel.classList.add('favicon-cell');
  12520.       urlLabel.classList.add('weakrtl');
  12521.       urlLabel.classList.add('url');
  12522.       urlLabel.setAttribute('title', this.url);
  12523.       urlLabel.textContent = this.url;
  12524.  
  12525.       // The favicon URL is prefixed with "origin/", which essentially removes
  12526.       // the URL path past the top-level domain and ensures that a scheme (e.g.,
  12527.       // http) is being used. This ensures that the favicon returned is the
  12528.       // default favicon for the domain and that the URL has a scheme if none
  12529.       // is present in the password manager.
  12530.       urlLabel.style.backgroundImage =
  12531.           url('chrome://favicon/origin/' + this.url);
  12532.       this.contentElement.appendChild(urlLabel);
  12533.  
  12534.       // The stored username.
  12535.       var usernameLabel = this.ownerDocument.createElement('div');
  12536.       usernameLabel.className = 'name';
  12537.       usernameLabel.textContent = this.username;
  12538.       this.contentElement.appendChild(usernameLabel);
  12539.  
  12540.       // The stored password.
  12541.       var passwordInputDiv = this.ownerDocument.createElement('div');
  12542.       passwordInputDiv.className = 'password';
  12543.  
  12544.       // The password input field.
  12545.       var passwordInput = this.ownerDocument.createElement('input');
  12546.       passwordInput.type = 'password';
  12547.       passwordInput.className = 'inactive-password';
  12548.       passwordInput.readOnly = true;
  12549.       passwordInput.value = showPasswords ? this.password : '********';
  12550.       passwordInputDiv.appendChild(passwordInput);
  12551.  
  12552.       // The show/hide button.
  12553.       if (showPasswords) {
  12554.         var button = this.ownerDocument.createElement('button');
  12555.         button.hidden = true;
  12556.         button.className = 'list-inline-button custom-appearance';
  12557.         button.textContent = loadTimeData.getString('passwordShowButton');
  12558.         button.addEventListener('click', this.onClick_, true);
  12559.         passwordInputDiv.appendChild(button);
  12560.       }
  12561.  
  12562.       this.contentElement.appendChild(passwordInputDiv);
  12563.     },
  12564.  
  12565.     /** @override */
  12566.     selectionChanged: function() {
  12567.       var passwordInput = this.querySelector('input[type=password]');
  12568.       var textInput = this.querySelector('input[type=text]');
  12569.       var input = passwordInput || textInput;
  12570.       var button = input.nextSibling;
  12571.       // |button| doesn't exist when passwords can't be shown.
  12572.       if (!button)
  12573.         return;
  12574.       if (this.selected) {
  12575.         input.classList.remove('inactive-password');
  12576.         button.hidden = false;
  12577.       } else {
  12578.         input.classList.add('inactive-password');
  12579.         button.hidden = true;
  12580.       }
  12581.     },
  12582.  
  12583.     /**
  12584.      * On-click event handler. Swaps the type of the input field from password
  12585.      * to text and back.
  12586.      * @private
  12587.      */
  12588.     onClick_: function(event) {
  12589.       // The password is the input element previous to the button.
  12590.       var button = event.currentTarget;
  12591.       var passwordInput = button.previousSibling;
  12592.       if (passwordInput.type == 'password') {
  12593.         passwordInput.type = 'text';
  12594.         button.textContent = loadTimeData.getString('passwordHideButton');
  12595.       } else {
  12596.         passwordInput.type = 'password';
  12597.         button.textContent = loadTimeData.getString('passwordShowButton');
  12598.       }
  12599.     },
  12600.  
  12601.     /**
  12602.      * Get and set the URL for the entry.
  12603.      * @type {string}
  12604.      */
  12605.     get url() {
  12606.       return this.dataItem[0];
  12607.     },
  12608.     set url(url) {
  12609.       this.dataItem[0] = url;
  12610.     },
  12611.  
  12612.     /**
  12613.      * Get and set the username for the entry.
  12614.      * @type {string}
  12615.      */
  12616.     get username() {
  12617.       return this.dataItem[1];
  12618.     },
  12619.     set username(username) {
  12620.       this.dataItem[1] = username;
  12621.     },
  12622.  
  12623.     /**
  12624.      * Get and set the password for the entry.
  12625.      * @type {string}
  12626.      */
  12627.     get password() {
  12628.       return this.dataItem[2];
  12629.     },
  12630.     set password(password) {
  12631.       this.dataItem[2] = password;
  12632.     },
  12633.   };
  12634.  
  12635.   /**
  12636.    * Creates a new PasswordExceptions list item.
  12637.    * @param {Array} entry A pair of the form [url, username].
  12638.    * @constructor
  12639.    * @extends {Deletable.ListItem}
  12640.    */
  12641.   function PasswordExceptionsListItem(entry) {
  12642.     var el = cr.doc.createElement('div');
  12643.     el.dataItem = entry;
  12644.     el.__proto__ = PasswordExceptionsListItem.prototype;
  12645.     el.decorate();
  12646.  
  12647.     return el;
  12648.   }
  12649.  
  12650.   PasswordExceptionsListItem.prototype = {
  12651.     __proto__: DeletableItem.prototype,
  12652.  
  12653.     /**
  12654.      * Call when an element is decorated as a list item.
  12655.      */
  12656.     decorate: function() {
  12657.       DeletableItem.prototype.decorate.call(this);
  12658.  
  12659.       // The URL of the site.
  12660.       var urlLabel = this.ownerDocument.createElement('div');
  12661.       urlLabel.className = 'url';
  12662.       urlLabel.classList.add('favicon-cell');
  12663.       urlLabel.classList.add('weakrtl');
  12664.       urlLabel.textContent = this.url;
  12665.  
  12666.       // The favicon URL is prefixed with "origin/", which essentially removes
  12667.       // the URL path past the top-level domain and ensures that a scheme (e.g.,
  12668.       // http) is being used. This ensures that the favicon returned is the
  12669.       // default favicon for the domain and that the URL has a scheme if none
  12670.       // is present in the password manager.
  12671.       urlLabel.style.backgroundImage =
  12672.           url('chrome://favicon/origin/' + this.url);
  12673.       this.contentElement.appendChild(urlLabel);
  12674.     },
  12675.  
  12676.     /**
  12677.      * Get the url for the entry.
  12678.      * @type {string}
  12679.      */
  12680.     get url() {
  12681.       return this.dataItem;
  12682.     },
  12683.     set url(url) {
  12684.       this.dataItem = url;
  12685.     },
  12686.   };
  12687.  
  12688.   /**
  12689.    * Create a new passwords list.
  12690.    * @constructor
  12691.    * @extends {cr.ui.List}
  12692.    */
  12693.   var PasswordsList = cr.ui.define('list');
  12694.  
  12695.   PasswordsList.prototype = {
  12696.     __proto__: DeletableItemList.prototype,
  12697.  
  12698.     /**
  12699.      * Whether passwords can be revealed or not.
  12700.      * @type {boolean}
  12701.      * @private
  12702.      */
  12703.     showPasswords_: true,
  12704.  
  12705.     /** @override */
  12706.     decorate: function() {
  12707.       DeletableItemList.prototype.decorate.call(this);
  12708.       Preferences.getInstance().addEventListener(
  12709.           'profile.password_manager_allow_show_passwords',
  12710.           this.onPreferenceChanged_.bind(this));
  12711.     },
  12712.  
  12713.     /**
  12714.      * Listener for changes on the preference.
  12715.      * @param {Event} event The preference update event.
  12716.      * @private
  12717.      */
  12718.     onPreferenceChanged_: function(event) {
  12719.       this.showPasswords_ = event.value.value;
  12720.       this.redraw();
  12721.     },
  12722.  
  12723.     /** @override */
  12724.     createItem: function(entry) {
  12725.       return new PasswordListItem(entry, this.showPasswords_);
  12726.     },
  12727.  
  12728.     /** @override */
  12729.     deleteItemAtIndex: function(index) {
  12730.       var item = this.dataModel.item(index);
  12731.       if (item && item.length > 3) {
  12732.         // The fourth element, if present, is the original index to delete.
  12733.         index = item[3];
  12734.       }
  12735.       PasswordManager.removeSavedPassword(index);
  12736.     },
  12737.  
  12738.     /**
  12739.      * The length of the list.
  12740.      */
  12741.     get length() {
  12742.       return this.dataModel.length;
  12743.     },
  12744.   };
  12745.  
  12746.   /**
  12747.    * Create a new passwords list.
  12748.    * @constructor
  12749.    * @extends {cr.ui.List}
  12750.    */
  12751.   var PasswordExceptionsList = cr.ui.define('list');
  12752.  
  12753.   PasswordExceptionsList.prototype = {
  12754.     __proto__: DeletableItemList.prototype,
  12755.  
  12756.     /** @override */
  12757.     createItem: function(entry) {
  12758.       return new PasswordExceptionsListItem(entry);
  12759.     },
  12760.  
  12761.     /** @override */
  12762.     deleteItemAtIndex: function(index) {
  12763.       PasswordManager.removePasswordException(index);
  12764.     },
  12765.  
  12766.     /**
  12767.      * The length of the list.
  12768.      */
  12769.     get length() {
  12770.       return this.dataModel.length;
  12771.     },
  12772.   };
  12773.  
  12774.   return {
  12775.     PasswordListItem: PasswordListItem,
  12776.     PasswordExceptionsListItem: PasswordExceptionsListItem,
  12777.     PasswordsList: PasswordsList,
  12778.     PasswordExceptionsList: PasswordExceptionsList,
  12779.   };
  12780. });
  12781.  
  12782. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12783. // Use of this source code is governed by a BSD-style license that can be
  12784. // found in the LICENSE file.
  12785.  
  12786. cr.define('options', function() {
  12787.   /** @const */ var ListItem = cr.ui.ListItem;
  12788.   /** @const */ var Grid = cr.ui.Grid;
  12789.   /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
  12790.  
  12791.   /**
  12792.    * Creates a new profile icon grid item.
  12793.    * @param {Object} iconURL The profile icon URL.
  12794.    * @constructor
  12795.    * @extends {cr.ui.GridItem}
  12796.    */
  12797.   function ProfilesIconGridItem(iconURL) {
  12798.     var el = cr.doc.createElement('span');
  12799.     el.iconURL_ = iconURL;
  12800.     ProfilesIconGridItem.decorate(el);
  12801.     return el;
  12802.   }
  12803.  
  12804.   /**
  12805.    * Decorates an element as a profile grid item.
  12806.    * @param {!HTMLElement} el The element to decorate.
  12807.    */
  12808.   ProfilesIconGridItem.decorate = function(el) {
  12809.     el.__proto__ = ProfilesIconGridItem.prototype;
  12810.     el.decorate();
  12811.   };
  12812.  
  12813.   ProfilesIconGridItem.prototype = {
  12814.     __proto__: ListItem.prototype,
  12815.  
  12816.     /** @override */
  12817.     decorate: function() {
  12818.       ListItem.prototype.decorate.call(this);
  12819.       var imageEl = cr.doc.createElement('img');
  12820.       imageEl.className = 'profile-icon';
  12821.       imageEl.src = this.iconURL_;
  12822.       this.appendChild(imageEl);
  12823.  
  12824.       this.className = 'profile-icon-grid-item';
  12825.     },
  12826.   };
  12827.  
  12828.   var ProfilesIconGrid = cr.ui.define('grid');
  12829.  
  12830.   ProfilesIconGrid.prototype = {
  12831.     __proto__: Grid.prototype,
  12832.  
  12833.     /** @override */
  12834.     decorate: function() {
  12835.       Grid.prototype.decorate.call(this);
  12836.       this.selectionModel = new ListSingleSelectionModel();
  12837.     },
  12838.  
  12839.     /** @override */
  12840.     createItem: function(iconURL) {
  12841.       return new ProfilesIconGridItem(iconURL);
  12842.     },
  12843.   };
  12844.  
  12845.   return {
  12846.     ProfilesIconGrid: ProfilesIconGrid
  12847.   };
  12848. });
  12849.  
  12850.  
  12851. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12852. // Use of this source code is governed by a BSD-style license that can be
  12853. // found in the LICENSE file.
  12854.  
  12855. cr.define('options', function() {
  12856.   /** @const */ var OptionsPage = options.OptionsPage;
  12857.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  12858.  
  12859.   /**
  12860.    * Encapsulated handling of search engine management page.
  12861.    * @constructor
  12862.    */
  12863.   function SearchEngineManager() {
  12864.     this.activeNavTab = null;
  12865.     OptionsPage.call(this, 'searchEngines',
  12866.                      loadTimeData.getString('searchEngineManagerPageTabTitle'),
  12867.                      'search-engine-manager-page');
  12868.   }
  12869.  
  12870.   cr.addSingletonGetter(SearchEngineManager);
  12871.  
  12872.   SearchEngineManager.prototype = {
  12873.     __proto__: OptionsPage.prototype,
  12874.  
  12875.     /**
  12876.      * List for default search engine options.
  12877.      * @private
  12878.      */
  12879.     defaultsList_: null,
  12880.  
  12881.     /**
  12882.      * List for other search engine options.
  12883.      * @private
  12884.      */
  12885.     othersList_: null,
  12886.  
  12887.     /**
  12888.      * List for extension keywords.
  12889.      * @private
  12890.     extensionList_ : null,
  12891.  
  12892.     /** inheritDoc */
  12893.     initializePage: function() {
  12894.       OptionsPage.prototype.initializePage.call(this);
  12895.  
  12896.       this.defaultsList_ = $('default-search-engine-list');
  12897.       this.setUpList_(this.defaultsList_);
  12898.  
  12899.       this.othersList_ = $('other-search-engine-list');
  12900.       this.setUpList_(this.othersList_);
  12901.  
  12902.       this.extensionList_ = $('extension-keyword-list');
  12903.       this.setUpList_(this.extensionList_);
  12904.  
  12905.       $('search-engine-manager-confirm').onclick = function() {
  12906.         OptionsPage.closeOverlay();
  12907.       };
  12908.     },
  12909.  
  12910.     /**
  12911.      * Sets up the given list as a search engine list
  12912.      * @param {List} list The list to set up.
  12913.      * @private
  12914.      */
  12915.     setUpList_: function(list) {
  12916.       options.search_engines.SearchEngineList.decorate(list);
  12917.       list.autoExpands = true;
  12918.     },
  12919.  
  12920.     /**
  12921.      * Updates the search engine list with the given entries.
  12922.      * @private
  12923.      * @param {Array} defaultEngines List of possible default search engines.
  12924.      * @param {Array} otherEngines List of other search engines.
  12925.      * @param {Array} keywords List of keywords from extensions.
  12926.      */
  12927.     updateSearchEngineList_: function(defaultEngines, otherEngines, keywords) {
  12928.       this.defaultsList_.dataModel = new ArrayDataModel(defaultEngines);
  12929.  
  12930.       otherEngines = otherEngines.map(function(x) {
  12931.         return [x, x.name.toLocaleLowerCase()];
  12932.       }).sort(function(a, b) {
  12933.         return a[1].localeCompare(b[1]);
  12934.       }).map(function(x) {
  12935.         return x[0];
  12936.       });
  12937.  
  12938.       var othersModel = new ArrayDataModel(otherEngines);
  12939.       // Add a "new engine" row.
  12940.       othersModel.push({
  12941.         'modelIndex': '-1',
  12942.         'canBeEdited': true
  12943.       });
  12944.       this.othersList_.dataModel = othersModel;
  12945.  
  12946.       if (keywords.length > 0) {
  12947.         $('extension-keyword-div').hidden = false;
  12948.         var extensionsModel = new ArrayDataModel(keywords);
  12949.         this.extensionList_.dataModel = extensionsModel;
  12950.       } else {
  12951.         $('extension-keyword-div').hidden = true;
  12952.       }
  12953.     },
  12954.   };
  12955.  
  12956.   SearchEngineManager.updateSearchEngineList = function(defaultEngines,
  12957.                                                         otherEngines,
  12958.                                                         keywords) {
  12959.     SearchEngineManager.getInstance().updateSearchEngineList_(defaultEngines,
  12960.                                                               otherEngines,
  12961.                                                               keywords);
  12962.   };
  12963.  
  12964.   SearchEngineManager.validityCheckCallback = function(validity, modelIndex) {
  12965.     // Forward to both lists; the one without a matching modelIndex will ignore
  12966.     // it.
  12967.     SearchEngineManager.getInstance().defaultsList_.validationComplete(
  12968.         validity, modelIndex);
  12969.     SearchEngineManager.getInstance().othersList_.validationComplete(
  12970.         validity, modelIndex);
  12971.   };
  12972.  
  12973.   // Export
  12974.   return {
  12975.     SearchEngineManager: SearchEngineManager
  12976.   };
  12977.  
  12978. });
  12979.  
  12980.  
  12981. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  12982. // Use of this source code is governed by a BSD-style license that can be
  12983. // found in the LICENSE file.
  12984.  
  12985. cr.define('options.search_engines', function() {
  12986.   /** @const */ var ControlledSettingIndicator =
  12987.                     options.ControlledSettingIndicator;
  12988.   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
  12989.   /** @const */ var InlineEditableItem = options.InlineEditableItem;
  12990.   /** @const */ var ListSelectionController = cr.ui.ListSelectionController;
  12991.  
  12992.   /**
  12993.    * Creates a new search engine list item.
  12994.    * @param {Object} searchEnigne The search engine this represents.
  12995.    * @constructor
  12996.    * @extends {cr.ui.ListItem}
  12997.    */
  12998.   function SearchEngineListItem(searchEngine) {
  12999.     var el = cr.doc.createElement('div');
  13000.     el.searchEngine_ = searchEngine;
  13001.     SearchEngineListItem.decorate(el);
  13002.     return el;
  13003.   }
  13004.  
  13005.   /**
  13006.    * Decorates an element as a search engine list item.
  13007.    * @param {!HTMLElement} el The element to decorate.
  13008.    */
  13009.   SearchEngineListItem.decorate = function(el) {
  13010.     el.__proto__ = SearchEngineListItem.prototype;
  13011.     el.decorate();
  13012.   };
  13013.  
  13014.   SearchEngineListItem.prototype = {
  13015.     __proto__: InlineEditableItem.prototype,
  13016.  
  13017.     /**
  13018.      * Input field for editing the engine name.
  13019.      * @type {HTMLElement}
  13020.      * @private
  13021.      */
  13022.     nameField_: null,
  13023.  
  13024.     /**
  13025.      * Input field for editing the engine keyword.
  13026.      * @type {HTMLElement}
  13027.      * @private
  13028.      */
  13029.     keywordField_: null,
  13030.  
  13031.     /**
  13032.      * Input field for editing the engine url.
  13033.      * @type {HTMLElement}
  13034.      * @private
  13035.      */
  13036.     urlField_: null,
  13037.  
  13038.     /**
  13039.      * Whether or not an input validation request is currently outstanding.
  13040.      * @type {boolean}
  13041.      * @private
  13042.      */
  13043.     waitingForValidation_: false,
  13044.  
  13045.     /**
  13046.      * Whether or not the current set of input is known to be valid.
  13047.      * @type {boolean}
  13048.      * @private
  13049.      */
  13050.     currentlyValid_: false,
  13051.  
  13052.     /** @override */
  13053.     decorate: function() {
  13054.       InlineEditableItem.prototype.decorate.call(this);
  13055.  
  13056.       var engine = this.searchEngine_;
  13057.  
  13058.       if (engine.modelIndex == '-1') {
  13059.         this.isPlaceholder = true;
  13060.         engine.name = '';
  13061.         engine.keyword = '';
  13062.         engine.url = '';
  13063.       }
  13064.  
  13065.       this.currentlyValid_ = !this.isPlaceholder;
  13066.  
  13067.       if (engine.default)
  13068.         this.classList.add('default');
  13069.  
  13070.       this.deletable = engine.canBeRemoved;
  13071.  
  13072.       // Construct the name column.
  13073.       var nameColEl = this.ownerDocument.createElement('div');
  13074.       nameColEl.className = 'name-column';
  13075.       nameColEl.classList.add('weakrtl');
  13076.       this.contentElement.appendChild(nameColEl);
  13077.  
  13078.       // Add the favicon.
  13079.       var faviconDivEl = this.ownerDocument.createElement('div');
  13080.       faviconDivEl.className = 'favicon';
  13081.       if (!this.isPlaceholder) {
  13082.         faviconDivEl.style.backgroundImage =
  13083.             url('chrome://favicon/iconurl@' + window.devicePixelRatio + 'x/' +
  13084.                 engine.iconURL);
  13085.       }
  13086.       nameColEl.appendChild(faviconDivEl);
  13087.  
  13088.       var nameEl = this.createEditableTextCell(engine.displayName);
  13089.       nameEl.classList.add('weakrtl');
  13090.       nameColEl.appendChild(nameEl);
  13091.  
  13092.       // Then the keyword column.
  13093.       var keywordEl = this.createEditableTextCell(engine.keyword);
  13094.       keywordEl.className = 'keyword-column';
  13095.       keywordEl.classList.add('weakrtl');
  13096.       this.contentElement.appendChild(keywordEl);
  13097.  
  13098.       // And the URL column.
  13099.       var urlEl = this.createEditableTextCell(engine.url);
  13100.       var urlWithButtonEl = this.ownerDocument.createElement('div');
  13101.       urlWithButtonEl.appendChild(urlEl);
  13102.       urlWithButtonEl.className = 'url-column';
  13103.       urlWithButtonEl.classList.add('weakrtl');
  13104.       this.contentElement.appendChild(urlWithButtonEl);
  13105.       // Add the Make Default button. Temporary until drag-and-drop re-ordering
  13106.       // is implemented. When this is removed, remove the extra div above.
  13107.       if (engine.canBeDefault) {
  13108.         var makeDefaultButtonEl = this.ownerDocument.createElement('button');
  13109.         makeDefaultButtonEl.className = 'custom-appearance list-inline-button';
  13110.         makeDefaultButtonEl.textContent =
  13111.             loadTimeData.getString('makeDefaultSearchEngineButton');
  13112.         makeDefaultButtonEl.onclick = function(e) {
  13113.           chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
  13114.         };
  13115.         // Don't select the row when clicking the button.
  13116.         makeDefaultButtonEl.onmousedown = function(e) {
  13117.           e.stopPropagation();
  13118.         };
  13119.         urlWithButtonEl.appendChild(makeDefaultButtonEl);
  13120.       }
  13121.  
  13122.       // Do final adjustment to the input fields.
  13123.       this.nameField_ = nameEl.querySelector('input');
  13124.       // The editable field uses the raw name, not the display name.
  13125.       this.nameField_.value = engine.name;
  13126.       this.keywordField_ = keywordEl.querySelector('input');
  13127.       this.urlField_ = urlEl.querySelector('input');
  13128.  
  13129.       if (engine.urlLocked)
  13130.         this.urlField_.disabled = true;
  13131.  
  13132.       if (this.isPlaceholder) {
  13133.         this.nameField_.placeholder =
  13134.             loadTimeData.getString('searchEngineTableNamePlaceholder');
  13135.         this.keywordField_.placeholder =
  13136.             loadTimeData.getString('searchEngineTableKeywordPlaceholder');
  13137.         this.urlField_.placeholder =
  13138.             loadTimeData.getString('searchEngineTableURLPlaceholder');
  13139.       }
  13140.  
  13141.       var fields = [this.nameField_, this.keywordField_, this.urlField_];
  13142.         for (var i = 0; i < fields.length; i++) {
  13143.         fields[i].oninput = this.startFieldValidation_.bind(this);
  13144.       }
  13145.  
  13146.       // Listen for edit events.
  13147.       if (engine.canBeEdited) {
  13148.         this.addEventListener('edit', this.onEditStarted_.bind(this));
  13149.         this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
  13150.         this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
  13151.       } else {
  13152.         this.editable = false;
  13153.         this.querySelector('.row-delete-button').hidden = true;
  13154.         var indicator = ControlledSettingIndicator();
  13155.         indicator.setAttribute('setting', 'search-engine');
  13156.         // Create a synthetic pref change event decorated as
  13157.         // CoreOptionsHandler::CreateValueForPref() does.
  13158.         var event = new cr.Event(this.contentType);
  13159.         event.value = { controlledBy: 'policy' };
  13160.         indicator.handlePrefChange(event);
  13161.         this.appendChild(indicator);
  13162.       }
  13163.     },
  13164.  
  13165.     /** @override */
  13166.     get currentInputIsValid() {
  13167.       return !this.waitingForValidation_ && this.currentlyValid_;
  13168.     },
  13169.  
  13170.     /** @override */
  13171.     get hasBeenEdited() {
  13172.       var engine = this.searchEngine_;
  13173.       return this.nameField_.value != engine.name ||
  13174.              this.keywordField_.value != engine.keyword ||
  13175.              this.urlField_.value != engine.url;
  13176.     },
  13177.  
  13178.     /**
  13179.      * Called when entering edit mode; starts an edit session in the model.
  13180.      * @param {Event} e The edit event.
  13181.      * @private
  13182.      */
  13183.     onEditStarted_: function(e) {
  13184.       var editIndex = this.searchEngine_.modelIndex;
  13185.       chrome.send('editSearchEngine', [String(editIndex)]);
  13186.       this.startFieldValidation_();
  13187.     },
  13188.  
  13189.     /**
  13190.      * Called when committing an edit; updates the model.
  13191.      * @param {Event} e The end event.
  13192.      * @private
  13193.      */
  13194.     onEditCommitted_: function(e) {
  13195.       chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
  13196.     },
  13197.  
  13198.     /**
  13199.      * Called when cancelling an edit; informs the model and resets the control
  13200.      * states.
  13201.      * @param {Event} e The cancel event.
  13202.      * @private
  13203.      */
  13204.     onEditCancelled_: function() {
  13205.       chrome.send('searchEngineEditCancelled');
  13206.  
  13207.       // The name field has been automatically set to match the display name,
  13208.       // but it should use the raw name instead.
  13209.       this.nameField_.value = this.searchEngine_.name;
  13210.       this.currentlyValid_ = !this.isPlaceholder;
  13211.     },
  13212.  
  13213.     /**
  13214.      * Returns the input field values as an array suitable for passing to
  13215.      * chrome.send. The order of the array is important.
  13216.      * @private
  13217.      * @return {array} The current input field values.
  13218.      */
  13219.     getInputFieldValues_: function() {
  13220.       return [this.nameField_.value,
  13221.               this.keywordField_.value,
  13222.               this.urlField_.value];
  13223.     },
  13224.  
  13225.     /**
  13226.      * Begins the process of asynchronously validing the input fields.
  13227.      * @private
  13228.      */
  13229.     startFieldValidation_: function() {
  13230.       this.waitingForValidation_ = true;
  13231.       var args = this.getInputFieldValues_();
  13232.       args.push(this.searchEngine_.modelIndex);
  13233.       chrome.send('checkSearchEngineInfoValidity', args);
  13234.     },
  13235.  
  13236.     /**
  13237.      * Callback for the completion of an input validition check.
  13238.      * @param {Object} validity A dictionary of validitation results.
  13239.      */
  13240.     validationComplete: function(validity) {
  13241.       this.waitingForValidation_ = false;
  13242.       // TODO(stuartmorgan): Implement the full validation UI with
  13243.       // checkmark/exclamation mark icons and tooltips showing the errors.
  13244.       if (validity.name) {
  13245.         this.nameField_.setCustomValidity('');
  13246.       } else {
  13247.         this.nameField_.setCustomValidity(
  13248.             loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
  13249.       }
  13250.  
  13251.       if (validity.keyword) {
  13252.         this.keywordField_.setCustomValidity('');
  13253.       } else {
  13254.         this.keywordField_.setCustomValidity(
  13255.             loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
  13256.       }
  13257.  
  13258.       if (validity.url) {
  13259.         this.urlField_.setCustomValidity('');
  13260.       } else {
  13261.         this.urlField_.setCustomValidity(
  13262.             loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
  13263.       }
  13264.  
  13265.       this.currentlyValid_ = validity.name && validity.keyword && validity.url;
  13266.     },
  13267.   };
  13268.  
  13269.   var SearchEngineList = cr.ui.define('list');
  13270.  
  13271.   SearchEngineList.prototype = {
  13272.     __proto__: InlineEditableItemList.prototype,
  13273.  
  13274.     /** @override */
  13275.     createItem: function(searchEngine) {
  13276.       return new SearchEngineListItem(searchEngine);
  13277.     },
  13278.  
  13279.     /** @override */
  13280.     deleteItemAtIndex: function(index) {
  13281.       var modelIndex = this.dataModel.item(index).modelIndex;
  13282.       chrome.send('removeSearchEngine', [String(modelIndex)]);
  13283.     },
  13284.  
  13285.     /**
  13286.      * Passes the results of an input validation check to the requesting row
  13287.      * if it's still being edited.
  13288.      * @param {number} modelIndex The model index of the item that was checked.
  13289.      * @param {Object} validity A dictionary of validitation results.
  13290.      */
  13291.     validationComplete: function(validity, modelIndex) {
  13292.       // If it's not still being edited, it no longer matters.
  13293.       var currentSelection = this.selectedItem;
  13294.       if (!currentSelection)
  13295.         return;
  13296.       var listItem = this.getListItem(currentSelection);
  13297.       if (listItem.editing && currentSelection.modelIndex == modelIndex)
  13298.         listItem.validationComplete(validity);
  13299.     },
  13300.   };
  13301.  
  13302.   // Export
  13303.   return {
  13304.     SearchEngineList: SearchEngineList
  13305.   };
  13306.  
  13307. });
  13308.  
  13309.  
  13310. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  13311. // Use of this source code is governed by a BSD-style license that can be
  13312. // found in the LICENSE file.
  13313.  
  13314. cr.define('options', function() {
  13315.   /** @const */ var OptionsPage = options.OptionsPage;
  13316.  
  13317.   /**
  13318.    * Encapsulated handling of a search bubble.
  13319.    * @constructor
  13320.    */
  13321.   function SearchBubble(text) {
  13322.     var el = cr.doc.createElement('div');
  13323.     SearchBubble.decorate(el);
  13324.     el.content = text;
  13325.     return el;
  13326.   }
  13327.  
  13328.   SearchBubble.decorate = function(el) {
  13329.     el.__proto__ = SearchBubble.prototype;
  13330.     el.decorate();
  13331.   };
  13332.  
  13333.   SearchBubble.prototype = {
  13334.     __proto__: HTMLDivElement.prototype,
  13335.  
  13336.     decorate: function() {
  13337.       this.className = 'search-bubble';
  13338.  
  13339.       this.innards_ = cr.doc.createElement('div');
  13340.       this.innards_.className = 'search-bubble-innards';
  13341.       this.appendChild(this.innards_);
  13342.  
  13343.       // We create a timer to periodically update the position of the bubbles.
  13344.       // While this isn't all that desirable, it's the only sure-fire way of
  13345.       // making sure the bubbles stay in the correct location as sections
  13346.       // may dynamically change size at any time.
  13347.       this.intervalId = setInterval(this.updatePosition.bind(this), 250);
  13348.     },
  13349.  
  13350.     /**
  13351.      * Sets the text message in the bubble.
  13352.      * @param {string} text The text the bubble will show.
  13353.      */
  13354.     set content(text) {
  13355.       this.innards_.textContent = text;
  13356.     },
  13357.  
  13358.     /**
  13359.      * Attach the bubble to the element.
  13360.      */
  13361.     attachTo: function(element) {
  13362.       var parent = element.parentElement;
  13363.       if (!parent)
  13364.         return;
  13365.       if (parent.tagName == 'TD') {
  13366.         // To make absolute positioning work inside a table cell we need
  13367.         // to wrap the bubble div into another div with position:relative.
  13368.         // This only works properly if the element is the first child of the
  13369.         // table cell which is true for all options pages.
  13370.         this.wrapper = cr.doc.createElement('div');
  13371.         this.wrapper.className = 'search-bubble-wrapper';
  13372.         this.wrapper.appendChild(this);
  13373.         parent.insertBefore(this.wrapper, element);
  13374.       } else {
  13375.         parent.insertBefore(this, element);
  13376.       }
  13377.     },
  13378.  
  13379.     /**
  13380.      * Clear the interval timer and remove the element from the page.
  13381.      */
  13382.     dispose: function() {
  13383.       clearInterval(this.intervalId);
  13384.  
  13385.       var child = this.wrapper || this;
  13386.       var parent = child.parentNode;
  13387.       if (parent)
  13388.         parent.removeChild(child);
  13389.     },
  13390.  
  13391.     /**
  13392.      * Update the position of the bubble.  Called at creation time and then
  13393.      * periodically while the bubble remains visible.
  13394.      */
  13395.     updatePosition: function() {
  13396.       // This bubble is 'owned' by the next sibling.
  13397.       var owner = (this.wrapper || this).nextSibling;
  13398.  
  13399.       // If there isn't an offset parent, we have nothing to do.
  13400.       if (!owner.offsetParent)
  13401.         return;
  13402.  
  13403.       // Position the bubble below the location of the owner.
  13404.       var left = owner.offsetLeft + owner.offsetWidth / 2 -
  13405.           this.offsetWidth / 2;
  13406.       var top = owner.offsetTop + owner.offsetHeight;
  13407.  
  13408.       // Update the position in the CSS.  Cache the last values for
  13409.       // best performance.
  13410.       if (left != this.lastLeft) {
  13411.         this.style.left = left + 'px';
  13412.         this.lastLeft = left;
  13413.       }
  13414.       if (top != this.lastTop) {
  13415.         this.style.top = top + 'px';
  13416.         this.lastTop = top;
  13417.       }
  13418.     },
  13419.   };
  13420.  
  13421.   /**
  13422.    * Encapsulated handling of the search page.
  13423.    * @constructor
  13424.    */
  13425.   function SearchPage() {
  13426.     OptionsPage.call(this, 'search',
  13427.                      loadTimeData.getString('searchPageTabTitle'),
  13428.                      'searchPage');
  13429.   }
  13430.  
  13431.   cr.addSingletonGetter(SearchPage);
  13432.  
  13433.   SearchPage.prototype = {
  13434.     // Inherit SearchPage from OptionsPage.
  13435.     __proto__: OptionsPage.prototype,
  13436.  
  13437.     /**
  13438.      * A boolean to prevent recursion. Used by setSearchText_().
  13439.      * @type {Boolean}
  13440.      * @private
  13441.      */
  13442.     insideSetSearchText_: false,
  13443.  
  13444.     /**
  13445.      * Initialize the page.
  13446.      */
  13447.     initializePage: function() {
  13448.       // Call base class implementation to start preference initialization.
  13449.       OptionsPage.prototype.initializePage.call(this);
  13450.  
  13451.       this.searchField = $('search-field');
  13452.  
  13453.       // Handle search events. (No need to throttle, WebKit's search field
  13454.       // will do that automatically.)
  13455.       this.searchField.onsearch = function(e) {
  13456.         this.setSearchText_(e.currentTarget.value);
  13457.       }.bind(this);
  13458.  
  13459.       // Install handler for key presses.
  13460.       document.addEventListener('keydown',
  13461.                                 this.keyDownEventHandler_.bind(this));
  13462.     },
  13463.  
  13464.     /** @override */
  13465.     get sticky() {
  13466.       return true;
  13467.     },
  13468.  
  13469.     /**
  13470.      * Called after this page has shown.
  13471.      */
  13472.     didShowPage: function() {
  13473.       // This method is called by the Options page after all pages have
  13474.       // had their visibilty attribute set.  At this point we can perform the
  13475.       // search specific DOM manipulation.
  13476.       this.setSearchActive_(true);
  13477.     },
  13478.  
  13479.     /**
  13480.      * Called before this page will be hidden.
  13481.      */
  13482.     willHidePage: function() {
  13483.       // This method is called by the Options page before all pages have
  13484.       // their visibilty attribute set.  Before that happens, we need to
  13485.       // undo the search specific DOM manipulation that was performed in
  13486.       // didShowPage.
  13487.       this.setSearchActive_(false);
  13488.     },
  13489.  
  13490.     /**
  13491.      * Update the UI to reflect whether we are in a search state.
  13492.      * @param {boolean} active True if we are on the search page.
  13493.      * @private
  13494.      */
  13495.     setSearchActive_: function(active) {
  13496.       // It's fine to exit if search wasn't active and we're not going to
  13497.       // activate it now.
  13498.       if (!this.searchActive_ && !active)
  13499.         return;
  13500.  
  13501.       this.searchActive_ = active;
  13502.  
  13503.       if (active) {
  13504.         var hash = location.hash;
  13505.         if (hash) {
  13506.           this.searchField.value =
  13507.               decodeURIComponent(hash.slice(1).replace(/\+/g, ' '));
  13508.         } else if (!this.searchField.value) {
  13509.           // This should only happen if the user goes directly to
  13510.           // chrome://settings-frame/search
  13511.           OptionsPage.showDefaultPage();
  13512.           return;
  13513.         }
  13514.  
  13515.         // Move 'advanced' sections into the main settings page to allow
  13516.         // searching.
  13517.         if (!this.advancedSections_) {
  13518.           this.advancedSections_ =
  13519.               $('advanced-settings-container').querySelectorAll('section');
  13520.           for (var i = 0, section; section = this.advancedSections_[i]; i++)
  13521.             $('settings').appendChild(section);
  13522.         }
  13523.       }
  13524.  
  13525.       var pagesToSearch = this.getSearchablePages_();
  13526.       for (var key in pagesToSearch) {
  13527.         var page = pagesToSearch[key];
  13528.  
  13529.         if (!active)
  13530.           page.visible = false;
  13531.  
  13532.         // Update the visible state of all top-level elements that are not
  13533.         // sections (ie titles, button strips).  We do this before changing
  13534.         // the page visibility to avoid excessive re-draw.
  13535.         for (var i = 0, childDiv; childDiv = page.pageDiv.children[i]; i++) {
  13536.           if (active) {
  13537.             if (childDiv.tagName != 'SECTION')
  13538.               childDiv.classList.add('search-hidden');
  13539.           } else {
  13540.             childDiv.classList.remove('search-hidden');
  13541.           }
  13542.         }
  13543.  
  13544.         if (active) {
  13545.           // When search is active, remove the 'hidden' tag.  This tag may have
  13546.           // been added by the OptionsPage.
  13547.           page.pageDiv.hidden = false;
  13548.         }
  13549.       }
  13550.  
  13551.       if (active) {
  13552.         this.setSearchText_(this.searchField.value);
  13553.         this.searchField.focus();
  13554.       } else {
  13555.         // After hiding all page content, remove any search results.
  13556.         this.unhighlightMatches_();
  13557.         this.removeSearchBubbles_();
  13558.  
  13559.         // Move 'advanced' sections back into their original container.
  13560.         if (this.advancedSections_) {
  13561.           for (var i = 0, section; section = this.advancedSections_[i]; i++)
  13562.             $('advanced-settings-container').appendChild(section);
  13563.           this.advancedSections_ = null;
  13564.         }
  13565.       }
  13566.     },
  13567.  
  13568.     /**
  13569.      * Set the current search criteria.
  13570.      * @param {string} text Search text.
  13571.      * @private
  13572.      */
  13573.     setSearchText_: function(text) {
  13574.       // Prevent recursive execution of this method.
  13575.       if (this.insideSetSearchText_) return;
  13576.       this.insideSetSearchText_ = true;
  13577.  
  13578.       // Cleanup the search query string.
  13579.       text = SearchPage.canonicalizeQuery(text);
  13580.  
  13581.       // Set the hash on the current page, and the enclosing uber page
  13582.       var hash = text ? '#' + encodeURIComponent(text) : '';
  13583.       var path = text ? this.name : '';
  13584.       window.location.hash = hash;
  13585.       uber.invokeMethodOnParent('setPath', {path: path + hash});
  13586.  
  13587.       // Toggle the search page if necessary.
  13588.       if (text) {
  13589.         if (!this.searchActive_)
  13590.           OptionsPage.showPageByName(this.name, false);
  13591.       } else {
  13592.         if (this.searchActive_)
  13593.           OptionsPage.showPageByName(OptionsPage.getDefaultPage().name, false);
  13594.  
  13595.         this.insideSetSearchText_ = false;
  13596.         return;
  13597.       }
  13598.  
  13599.       var foundMatches = false;
  13600.  
  13601.       // Remove any prior search results.
  13602.       this.unhighlightMatches_();
  13603.       this.removeSearchBubbles_();
  13604.  
  13605.       var pagesToSearch = this.getSearchablePages_();
  13606.       for (var key in pagesToSearch) {
  13607.         var page = pagesToSearch[key];
  13608.         var elements = page.pageDiv.querySelectorAll('section');
  13609.         for (var i = 0, node; node = elements[i]; i++) {
  13610.           node.classList.add('search-hidden');
  13611.         }
  13612.       }
  13613.  
  13614.       var bubbleControls = [];
  13615.  
  13616.       // Generate search text by applying lowercase and escaping any characters
  13617.       // that would be problematic for regular expressions.
  13618.       var searchText =
  13619.           text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
  13620.       // Generate a regular expression for hilighting search terms.
  13621.       var regExp = new RegExp('(' + searchText + ')', 'ig');
  13622.  
  13623.       if (searchText.length) {
  13624.         // Search all top-level sections for anchored string matches.
  13625.         for (var key in pagesToSearch) {
  13626.           var page = pagesToSearch[key];
  13627.           var elements =
  13628.               page.pageDiv.querySelectorAll('section');
  13629.           for (var i = 0, node; node = elements[i]; i++) {
  13630.             if (this.highlightMatches_(regExp, node)) {
  13631.               node.classList.remove('search-hidden');
  13632.               if (!node.hidden)
  13633.                 foundMatches = true;
  13634.             }
  13635.           }
  13636.         }
  13637.  
  13638.         // Search all sub-pages, generating an array of top-level sections that
  13639.         // we need to make visible.
  13640.         var subPagesToSearch = this.getSearchableSubPages_();
  13641.         var control, node;
  13642.         for (var key in subPagesToSearch) {
  13643.           var page = subPagesToSearch[key];
  13644.           if (this.highlightMatches_(regExp, page.pageDiv)) {
  13645.             this.revealAssociatedSections_(page);
  13646.  
  13647.             bubbleControls =
  13648.                 bubbleControls.concat(this.getAssociatedControls_(page));
  13649.  
  13650.             foundMatches = true;
  13651.           }
  13652.         }
  13653.       }
  13654.  
  13655.       // Configure elements on the search results page based on search results.
  13656.       $('searchPageNoMatches').hidden = foundMatches;
  13657.  
  13658.       // Create search balloons for sub-page results.
  13659.       length = bubbleControls.length;
  13660.       for (var i = 0; i < length; i++)
  13661.         this.createSearchBubble_(bubbleControls[i], text);
  13662.  
  13663.       // Cleanup the recursion-prevention variable.
  13664.       this.insideSetSearchText_ = false;
  13665.     },
  13666.  
  13667.     /**
  13668.      * Reveal the associated section for |subpage|, as well as the one for its
  13669.      * |parentPage|, and its |parentPage|'s |parentPage|, etc.
  13670.      * @private
  13671.      */
  13672.     revealAssociatedSections_: function(subpage) {
  13673.       for (var page = subpage; page; page = page.parentPage) {
  13674.         var section = page.associatedSection;
  13675.         if (section)
  13676.           section.classList.remove('search-hidden');
  13677.       }
  13678.     },
  13679.  
  13680.     /**
  13681.      * @return {!Array.<HTMLElement>} all the associated controls for |subpage|,
  13682.      * including |subpage.associatedControls| as well as any controls on parent
  13683.      * pages that are indirectly necessary to get to the subpage.
  13684.      * @private
  13685.      */
  13686.     getAssociatedControls_: function(subpage) {
  13687.       var controls = [];
  13688.       for (var page = subpage; page; page = page.parentPage) {
  13689.         if (page.associatedControls)
  13690.           controls = controls.concat(page.associatedControls);
  13691.       }
  13692.       return controls;
  13693.     },
  13694.  
  13695.     /**
  13696.      * Wraps matches in spans.
  13697.      * @param {RegExp} regExp The search query (in regexp form).
  13698.      * @param {Element} element An HTML container element to recursively search
  13699.      *     within.
  13700.      * @return {boolean} true if the element was changed.
  13701.      * @private
  13702.      */
  13703.     highlightMatches_: function(regExp, element) {
  13704.       var found = false;
  13705.       var div, child, tmp;
  13706.  
  13707.       // Walk the tree, searching each TEXT node.
  13708.       var walker = document.createTreeWalker(element,
  13709.                                              NodeFilter.SHOW_TEXT,
  13710.                                              null,
  13711.                                              false);
  13712.       var node = walker.nextNode();
  13713.       while (node) {
  13714.         var textContent = node.nodeValue;
  13715.         // Perform a search and replace on the text node value.
  13716.         var split = textContent.split(regExp);
  13717.         if (split.length > 1) {
  13718.           found = true;
  13719.           var nextNode = walker.nextNode();
  13720.           var parentNode = node.parentNode;
  13721.           // Use existing node as placeholder to determine where to insert the
  13722.           // replacement content.
  13723.           for (var i = 0; i < split.length; ++i) {
  13724.             if (i % 2 == 0) {
  13725.               parentNode.insertBefore(document.createTextNode(split[i]), node);
  13726.             } else {
  13727.               var span = document.createElement('span');
  13728.               span.className = 'search-highlighted';
  13729.               span.textContent = split[i];
  13730.               parentNode.insertBefore(span, node);
  13731.             }
  13732.           }
  13733.           // Remove old node.
  13734.           parentNode.removeChild(node);
  13735.           node = nextNode;
  13736.         } else {
  13737.           node = walker.nextNode();
  13738.         }
  13739.       }
  13740.  
  13741.       return found;
  13742.     },
  13743.  
  13744.     /**
  13745.      * Removes all search highlight tags from the document.
  13746.      * @private
  13747.      */
  13748.     unhighlightMatches_: function() {
  13749.       // Find all search highlight elements.
  13750.       var elements = document.querySelectorAll('.search-highlighted');
  13751.  
  13752.       // For each element, remove the highlighting.
  13753.       var parent, i;
  13754.       for (var i = 0, node; node = elements[i]; i++) {
  13755.         parent = node.parentNode;
  13756.  
  13757.         // Replace the highlight element with the first child (the text node).
  13758.         parent.replaceChild(node.firstChild, node);
  13759.  
  13760.         // Normalize the parent so that multiple text nodes will be combined.
  13761.         parent.normalize();
  13762.       }
  13763.     },
  13764.  
  13765.     /**
  13766.      * Creates a search result bubble attached to an element.
  13767.      * @param {Element} element An HTML element, usually a button.
  13768.      * @param {string} text A string to show in the bubble.
  13769.      * @private
  13770.      */
  13771.     createSearchBubble_: function(element, text) {
  13772.       // avoid appending multiple bubbles to a button.
  13773.       var sibling = element.previousElementSibling;
  13774.       if (sibling && (sibling.classList.contains('search-bubble') ||
  13775.                       sibling.classList.contains('search-bubble-wrapper')))
  13776.         return;
  13777.  
  13778.       var parent = element.parentElement;
  13779.       if (parent) {
  13780.         var bubble = new SearchBubble(text);
  13781.         bubble.attachTo(element);
  13782.         bubble.updatePosition();
  13783.       }
  13784.     },
  13785.  
  13786.     /**
  13787.      * Removes all search match bubbles.
  13788.      * @private
  13789.      */
  13790.     removeSearchBubbles_: function() {
  13791.       var elements = document.querySelectorAll('.search-bubble');
  13792.       var length = elements.length;
  13793.       for (var i = 0; i < length; i++)
  13794.         elements[i].dispose();
  13795.     },
  13796.  
  13797.     /**
  13798.      * Builds a list of top-level pages to search.  Omits the search page and
  13799.      * all sub-pages.
  13800.      * @return {Array} An array of pages to search.
  13801.      * @private
  13802.      */
  13803.     getSearchablePages_: function() {
  13804.       var name, page, pages = [];
  13805.       for (name in OptionsPage.registeredPages) {
  13806.         if (name != this.name) {
  13807.           page = OptionsPage.registeredPages[name];
  13808.           if (!page.parentPage)
  13809.             pages.push(page);
  13810.         }
  13811.       }
  13812.       return pages;
  13813.     },
  13814.  
  13815.     /**
  13816.      * Builds a list of sub-pages (and overlay pages) to search.  Ignore pages
  13817.      * that have no associated controls.
  13818.      * @return {Array} An array of pages to search.
  13819.      * @private
  13820.      */
  13821.     getSearchableSubPages_: function() {
  13822.       var name, pageInfo, page, pages = [];
  13823.       for (name in OptionsPage.registeredPages) {
  13824.         page = OptionsPage.registeredPages[name];
  13825.         if (page.parentPage && page.associatedSection)
  13826.           pages.push(page);
  13827.       }
  13828.       for (name in OptionsPage.registeredOverlayPages) {
  13829.         page = OptionsPage.registeredOverlayPages[name];
  13830.         if (page.associatedSection && page.pageDiv != undefined)
  13831.           pages.push(page);
  13832.       }
  13833.       return pages;
  13834.     },
  13835.  
  13836.     /**
  13837.      * A function to handle key press events.
  13838.      * @return {Event} a keydown event.
  13839.      * @private
  13840.      */
  13841.     keyDownEventHandler_: function(event) {
  13842.       /** @const */ var ESCAPE_KEY_CODE = 27;
  13843.       /** @const */ var FORWARD_SLASH_KEY_CODE = 191;
  13844.  
  13845.       switch (event.keyCode) {
  13846.         case ESCAPE_KEY_CODE:
  13847.           if (event.target == this.searchField) {
  13848.             this.setSearchText_('');
  13849.             this.searchField.blur();
  13850.             event.stopPropagation();
  13851.             event.preventDefault();
  13852.           }
  13853.           break;
  13854.         case FORWARD_SLASH_KEY_CODE:
  13855.           if (!/INPUT|SELECT|BUTTON|TEXTAREA/.test(event.target.tagName) &&
  13856.               !event.ctrlKey && !event.altKey) {
  13857.             this.searchField.focus();
  13858.             event.stopPropagation();
  13859.             event.preventDefault();
  13860.           }
  13861.           break;
  13862.       }
  13863.     },
  13864.   };
  13865.  
  13866.   /**
  13867.    * Standardizes a user-entered text query by removing extra whitespace.
  13868.    * @param {string} The user-entered text.
  13869.    * @return {string} The trimmed query.
  13870.    */
  13871.   SearchPage.canonicalizeQuery = function(text) {
  13872.     // Trim beginning and ending whitespace.
  13873.     return text.replace(/^\s+|\s+$/g, '');
  13874.   };
  13875.  
  13876.   // Export
  13877.   return {
  13878.     SearchPage: SearchPage
  13879.   };
  13880.  
  13881. });
  13882.  
  13883. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  13884. // Use of this source code is governed by a BSD-style license that can be
  13885. // found in the LICENSE file.
  13886.  
  13887. cr.define('options', function() {
  13888.   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
  13889.   /** @const */ var OptionsPage = options.OptionsPage;
  13890.   /** @const */ var SettingsDialog = options.SettingsDialog;
  13891.  
  13892.   /**
  13893.    * StartupOverlay class
  13894.    * Encapsulated handling of the 'Set Startup pages' overlay page.
  13895.    * @constructor
  13896.    * @class
  13897.    */
  13898.   function StartupOverlay() {
  13899.     SettingsDialog.call(this, 'startup',
  13900.                         loadTimeData.getString('startupPagesOverlayTabTitle'),
  13901.                         'startup-overlay',
  13902.                         $('startup-overlay-confirm'),
  13903.                         $('startup-overlay-cancel'));
  13904.   };
  13905.  
  13906.   cr.addSingletonGetter(StartupOverlay);
  13907.  
  13908.   StartupOverlay.prototype = {
  13909.     __proto__: SettingsDialog.prototype,
  13910.  
  13911.     /**
  13912.      * An autocomplete list that can be attached to a text field during editing.
  13913.      * @type {HTMLElement}
  13914.      * @private
  13915.      */
  13916.     autocompleteList_: null,
  13917.  
  13918.     startup_pages_pref_: {
  13919.       'name': 'session.urls_to_restore_on_startup',
  13920.       'disabled': false
  13921.     },
  13922.  
  13923.     /**
  13924.      * Initialize the page.
  13925.      */
  13926.     initializePage: function() {
  13927.       SettingsDialog.prototype.initializePage.call(this);
  13928.  
  13929.       var self = this;
  13930.  
  13931.       var startupPagesList = $('startupPagesList');
  13932.       options.browser_options.StartupPageList.decorate(startupPagesList);
  13933.       startupPagesList.autoExpands = true;
  13934.  
  13935.       $('startupUseCurrentButton').onclick = function(event) {
  13936.         chrome.send('setStartupPagesToCurrentPages');
  13937.       };
  13938.  
  13939.       Preferences.getInstance().addEventListener(
  13940.           this.startup_pages_pref_.name,
  13941.           this.handleStartupPageListChange_.bind(this));
  13942.  
  13943.       var suggestionList = new cr.ui.AutocompleteList();
  13944.       suggestionList.autoExpands = true;
  13945.       suggestionList.suggestionUpdateRequestCallback =
  13946.           this.requestAutocompleteSuggestions_.bind(this);
  13947.       $('startup-overlay').appendChild(suggestionList);
  13948.       this.autocompleteList_ = suggestionList;
  13949.       startupPagesList.autocompleteList = suggestionList;
  13950.     },
  13951.  
  13952.     /** @override */
  13953.     handleConfirm: function() {
  13954.       SettingsDialog.prototype.handleConfirm.call(this);
  13955.       chrome.send('commitStartupPrefChanges');
  13956.     },
  13957.  
  13958.     /** @override */
  13959.     handleCancel: function() {
  13960.       SettingsDialog.prototype.handleCancel.call(this);
  13961.       chrome.send('cancelStartupPrefChanges');
  13962.     },
  13963.  
  13964.     /**
  13965.      * Sets the enabled state of the custom startup page list
  13966.      * @param {boolean} disable True to disable, false to enable
  13967.      */
  13968.     setControlsDisabled: function(disable) {
  13969.       var startupPagesList = $('startupPagesList');
  13970.       startupPagesList.disabled = disable;
  13971.       startupPagesList.setAttribute('tabindex', disable ? -1 : 0);
  13972.       // Explicitly set disabled state for input text elements.
  13973.       var inputs = startupPagesList.querySelectorAll("input[type='text']");
  13974.       for (var i = 0; i < inputs.length; i++)
  13975.         inputs[i].disabled = disable;
  13976.       $('startupUseCurrentButton').disabled = disable;
  13977.     },
  13978.  
  13979.     /**
  13980.      * Enables or disables the the custom startup page list controls
  13981.      * based on the whether the 'pages to restore on startup' pref is enabled.
  13982.      */
  13983.     updateControlStates: function() {
  13984.       this.setControlsDisabled(
  13985.           this.startup_pages_pref_.disabled);
  13986.     },
  13987.  
  13988.     /**
  13989.      * Handles change events of the preference
  13990.      * 'session.urls_to_restore_on_startup'.
  13991.      * @param {event} preference changed event.
  13992.      * @private
  13993.      */
  13994.     handleStartupPageListChange_: function(event) {
  13995.       this.startup_pages_pref_.disabled = event.value.disabled;
  13996.       this.updateControlStates();
  13997.     },
  13998.  
  13999.     /**
  14000.      * Updates the startup pages list with the given entries.
  14001.      * @param {Array} pages List of startup pages.
  14002.      * @private
  14003.      */
  14004.     updateStartupPages_: function(pages) {
  14005.       var model = new ArrayDataModel(pages);
  14006.       // Add a "new page" row.
  14007.       model.push({
  14008.         'modelIndex': '-1'
  14009.       });
  14010.       $('startupPagesList').dataModel = model;
  14011.     },
  14012.  
  14013.     /**
  14014.      * Sends an asynchronous request for new autocompletion suggestions for the
  14015.      * the given query. When new suggestions are available, the C++ handler will
  14016.      * call updateAutocompleteSuggestions_.
  14017.      * @param {string} query List of autocomplete suggestions.
  14018.      * @private
  14019.      */
  14020.     requestAutocompleteSuggestions_: function(query) {
  14021.       chrome.send('requestAutocompleteSuggestionsForStartupPages', [query]);
  14022.     },
  14023.  
  14024.     /**
  14025.      * Updates the autocomplete suggestion list with the given entries.
  14026.      * @param {Array} pages List of autocomplete suggestions.
  14027.      * @private
  14028.      */
  14029.     updateAutocompleteSuggestions_: function(suggestions) {
  14030.       var list = this.autocompleteList_;
  14031.       // If the trigger for this update was a value being selected from the
  14032.       // current list, do nothing.
  14033.       if (list.targetInput && list.selectedItem &&
  14034.           list.selectedItem.url == list.targetInput.value) {
  14035.         return;
  14036.       }
  14037.       list.suggestions = suggestions;
  14038.     },
  14039.   };
  14040.  
  14041.   // Forward public APIs to private implementations.
  14042.   [
  14043.     'updateStartupPages',
  14044.     'updateAutocompleteSuggestions',
  14045.   ].forEach(function(name) {
  14046.     StartupOverlay[name] = function() {
  14047.       var instance = StartupOverlay.getInstance();
  14048.       return instance[name + '_'].apply(instance, arguments);
  14049.     };
  14050.   });
  14051.  
  14052.   // Export
  14053.   return {
  14054.     StartupOverlay: StartupOverlay
  14055.   };
  14056. });
  14057.  
  14058. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  14059. // Use of this source code is governed by a BSD-style license that can be
  14060. // found in the LICENSE file.
  14061.  
  14062. cr.define('options', function() {
  14063.   /** @const */ var OptionsPage = options.OptionsPage;
  14064.  
  14065.   // Variable to track if a captcha challenge was issued. If this gets set to
  14066.   // true, it stays that way until we are told about successful login from
  14067.   // the browser.  This means subsequent errors (like invalid password) are
  14068.   // rendered in the captcha state, which is basically identical except we
  14069.   // don't show the top error blurb 'Error Signing in' or the 'Create
  14070.   // account' link.
  14071.   var captchaChallengeActive_ = false;
  14072.  
  14073.   // When true, the password value may be empty when submitting auth info.
  14074.   // This is true when requesting an access code or when requesting an OTP or
  14075.   // captcha with the oauth sign in flow.
  14076.   var allowEmptyPassword_ = false;
  14077.  
  14078.   // True if the synced account uses a custom passphrase.
  14079.   var usePassphrase_ = false;
  14080.  
  14081.   // True if the synced account uses 'encrypt everything'.
  14082.   var useEncryptEverything_ = false;
  14083.  
  14084.   // True if the support for keystore encryption is enabled. Controls whether
  14085.   // the new unified encryption UI is displayed instead of the old encryption
  14086.   // ui (where passphrase and encrypted types could be set independently of
  14087.   // each other).
  14088.   var keystoreEncryptionEnabled_ = false;
  14089.  
  14090.   // The last email address that this profile was connected to.  If the profile
  14091.   // was never connected this is an empty string.  Otherwise it is a normalized
  14092.   // email address.
  14093.   var lastEmailAddress_ = '';
  14094.  
  14095.   /**
  14096.    * SyncSetupOverlay class
  14097.    * Encapsulated handling of the 'Sync Setup' overlay page.
  14098.    * @class
  14099.    */
  14100.   function SyncSetupOverlay() {
  14101.     OptionsPage.call(this, 'syncSetup',
  14102.                      loadTimeData.getString('syncSetupOverlayTabTitle'),
  14103.                      'sync-setup-overlay');
  14104.   }
  14105.  
  14106.   cr.addSingletonGetter(SyncSetupOverlay);
  14107.  
  14108.   SyncSetupOverlay.prototype = {
  14109.     __proto__: OptionsPage.prototype,
  14110.  
  14111.     /**
  14112.      * Initializes the page.
  14113.      */
  14114.     initializePage: function() {
  14115.       OptionsPage.prototype.initializePage.call(this);
  14116.  
  14117.       var self = this;
  14118.       $('gaia-login-form').onsubmit = function() {
  14119.         self.sendCredentialsAndClose_();
  14120.         return false;
  14121.       };
  14122.       $('google-option').onchange = $('explicit-option').onchange = function() {
  14123.         self.onPassphraseRadioChanged_();
  14124.       };
  14125.       $('basic-encryption-option').onchange =
  14126.           $('full-encryption-option').onchange = function() {
  14127.         self.onEncryptionRadioChanged_();
  14128.       }
  14129.       $('choose-datatypes-cancel').onclick =
  14130.           $('sync-setup-cancel').onclick =
  14131.           $('confirm-everything-cancel').onclick =
  14132.           $('stop-syncing-cancel').onclick =
  14133.           $('sync-spinner-cancel').onclick = function() {
  14134.         self.closeOverlay_();
  14135.       };
  14136.       $('confirm-everything-ok').onclick = function() {
  14137.         self.sendConfiguration_();
  14138.       };
  14139.       $('timeout-ok').onclick = function() {
  14140.         chrome.send('CloseTimeout');
  14141.         self.closeOverlay_();
  14142.       };
  14143.       $('stop-syncing-ok').onclick = function() {
  14144.         chrome.send('SyncSetupStopSyncing');
  14145.         self.closeOverlay_();
  14146.       };
  14147.       $('different-email').innerHTML = loadTimeData.getString('differentEmail');
  14148.     },
  14149.  
  14150.     showOverlay_: function() {
  14151.       OptionsPage.navigateToPage('syncSetup');
  14152.     },
  14153.  
  14154.     closeOverlay_: function() {
  14155.       OptionsPage.closeOverlay();
  14156.     },
  14157.  
  14158.     /** @override */
  14159.     didShowPage: function() {
  14160.       var forceLogin = document.location.hash == '#forceLogin';
  14161.       var result = JSON.stringify({'forceLogin': forceLogin});
  14162.       chrome.send('SyncSetupAttachHandler', [result]);
  14163.     },
  14164.  
  14165.     /** @override */
  14166.     didClosePage: function() {
  14167.       chrome.send('SyncSetupDidClosePage');
  14168.     },
  14169.  
  14170.     getEncryptionRadioCheckedValue_: function() {
  14171.       var f = $('choose-data-types-form');
  14172.       for (var i = 0; i < f.encrypt.length; ++i) {
  14173.         if (f.encrypt[i].checked)
  14174.           return f.encrypt[i].value;
  14175.       }
  14176.  
  14177.       return undefined;
  14178.     },
  14179.  
  14180.     getPassphraseRadioCheckedValue_: function() {
  14181.       var f = $('choose-data-types-form');
  14182.       for (var i = 0; i < f.option.length; ++i) {
  14183.         if (f.option[i].checked) {
  14184.           return f.option[i].value;
  14185.         }
  14186.       }
  14187.  
  14188.       return undefined;
  14189.     },
  14190.  
  14191.     disableEncryptionRadioGroup_: function() {
  14192.       var f = $('choose-data-types-form');
  14193.       for (var i = 0; i < f.encrypt.length; ++i)
  14194.         f.encrypt[i].disabled = true;
  14195.     },
  14196.  
  14197.     onPassphraseRadioChanged_: function() {
  14198.       var visible = this.getPassphraseRadioCheckedValue_() == 'explicit';
  14199.       $('sync-custom-passphrase').hidden = !visible;
  14200.     },
  14201.  
  14202.     onEncryptionRadioChanged_: function() {
  14203.       var visible = $('full-encryption-option').checked;
  14204.       $('sync-custom-passphrase').hidden = !visible;
  14205.     },
  14206.  
  14207.     checkAllDataTypeCheckboxes_: function() {
  14208.       // Only check the visible ones (since there's no way to uncheck
  14209.       // the invisible ones).
  14210.       var checkboxes = $('choose-data-types-body').querySelectorAll(
  14211.           '.sync-type-checkbox:not([hidden]) input');
  14212.       for (var i = 0; i < checkboxes.length; i++) {
  14213.         checkboxes[i].checked = true;
  14214.       }
  14215.     },
  14216.  
  14217.     setDataTypeCheckboxesEnabled_: function(enabled) {
  14218.       var checkboxes = $('choose-data-types-body').querySelectorAll('input');
  14219.       for (var i = 0; i < checkboxes.length; i++) {
  14220.         checkboxes[i].disabled = !enabled;
  14221.       }
  14222.     },
  14223.  
  14224.     setCheckboxesToKeepEverythingSynced_: function(value) {
  14225.       this.setDataTypeCheckboxesEnabled_(!value);
  14226.       if (value)
  14227.         this.checkAllDataTypeCheckboxes_();
  14228.     },
  14229.  
  14230.     // Returns true if none of the visible checkboxes are checked.
  14231.     noDataTypesChecked_: function() {
  14232.       var query = '.sync-type-checkbox:not([hidden]) input:checked';
  14233.       var checkboxes = $('choose-data-types-body').querySelectorAll(query);
  14234.       return checkboxes.length == 0;
  14235.     },
  14236.  
  14237.     checkPassphraseMatch_: function() {
  14238.       var emptyError = $('empty-error');
  14239.       var mismatchError = $('mismatch-error');
  14240.       emptyError.hidden = true;
  14241.       mismatchError.hidden = true;
  14242.  
  14243.       var f = $('choose-data-types-form');
  14244.       if ((this.getPassphraseRadioCheckedValue_() != 'explicit' ||
  14245.            $('google-option').disabled) &&
  14246.           (!$('full-encryption-option').checked ||
  14247.            $('basic-encryption-option').disabled)) {
  14248.         return true;
  14249.       }
  14250.  
  14251.       var customPassphrase = $('custom-passphrase');
  14252.       if (customPassphrase.value.length == 0) {
  14253.         emptyError.hidden = false;
  14254.         return false;
  14255.       }
  14256.  
  14257.       var confirmPassphrase = $('confirm-passphrase');
  14258.       if (confirmPassphrase.value != customPassphrase.value) {
  14259.         mismatchError.hidden = false;
  14260.         return false;
  14261.       }
  14262.  
  14263.       return true;
  14264.     },
  14265.  
  14266.     sendConfiguration_: function() {
  14267.       // Trying to submit, so hide previous errors.
  14268.       $('error-text').hidden = true;
  14269.  
  14270.       var syncAll = $('sync-select-datatypes').selectedIndex == 0;
  14271.       if (!syncAll && this.noDataTypesChecked_()) {
  14272.         $('error-text').hidden = false;
  14273.         return;
  14274.       }
  14275.  
  14276.       var encryptAllData = this.getEncryptionRadioCheckedValue_() == 'all';
  14277.       if (!encryptAllData &&
  14278.           $('full-encryption-option').checked &&
  14279.           this.keystoreEncryptionEnabled_) {
  14280.         encryptAllData = true;
  14281.       }
  14282.  
  14283.       var usePassphrase;
  14284.       var customPassphrase;
  14285.       var googlePassphrase = false;
  14286.       if (!$('sync-existing-passphrase-container').hidden) {
  14287.         // If we were prompted for an existing passphrase, use it.
  14288.         customPassphrase = $('choose-data-types-form').passphrase.value;
  14289.         usePassphrase = true;
  14290.         // If we were displaying the 'enter your old google password' prompt,
  14291.         // then that means this is the user's google password.
  14292.         googlePassphrase = !$('google-passphrase-needed-body').hidden;
  14293.         // We allow an empty passphrase, in case the user has disabled
  14294.         // all their encrypted datatypes. In that case, the PSS will accept
  14295.         // the passphrase and finish configuration. If the user has enabled
  14296.         // encrypted datatypes, the PSS will prompt again specifying that the
  14297.         // passphrase failed.
  14298.       } else if ((!$('google-option').disabled &&
  14299.                   this.getPassphraseRadioCheckedValue_() == 'explicit') ||
  14300.                  (!$('basic-encryption-option').disabled &&
  14301.                   $('full-encryption-option').checked)) {
  14302.         // The user is setting a custom passphrase for the first time.
  14303.         if (!this.checkPassphraseMatch_())
  14304.           return;
  14305.         customPassphrase = $('custom-passphrase').value;
  14306.         usePassphrase = true;
  14307.       } else {
  14308.         // The user is not setting a custom passphrase.
  14309.         usePassphrase = false;
  14310.       }
  14311.  
  14312.       // Don't allow the user to tweak the settings once we send the
  14313.       // configuration to the backend.
  14314.       this.setInputElementsDisabledState_(true);
  14315.       this.animateDisableLink_($('use-default-link'), true, null);
  14316.  
  14317.       // These values need to be kept in sync with where they are read in
  14318.       // SyncSetupFlow::GetDataTypeChoiceData().
  14319.       var result = JSON.stringify({
  14320.         'syncAllDataTypes': syncAll,
  14321.         'bookmarksSynced': syncAll || $('bookmarks-checkbox').checked,
  14322.         'preferencesSynced': syncAll || $('preferences-checkbox').checked,
  14323.         'themesSynced': syncAll || $('themes-checkbox').checked,
  14324.         'passwordsSynced': syncAll || $('passwords-checkbox').checked,
  14325.         'autofillSynced': syncAll || $('autofill-checkbox').checked,
  14326.         'extensionsSynced': syncAll || $('extensions-checkbox').checked,
  14327.         'typedUrlsSynced': syncAll || $('typed-urls-checkbox').checked,
  14328.         'appsSynced': syncAll || $('apps-checkbox').checked,
  14329.         'sessionsSynced': syncAll || $('sessions-checkbox').checked,
  14330.         'encryptAllData': encryptAllData,
  14331.         'usePassphrase': usePassphrase,
  14332.         'isGooglePassphrase': googlePassphrase,
  14333.         'passphrase': customPassphrase
  14334.       });
  14335.       chrome.send('SyncSetupConfigure', [result]);
  14336.     },
  14337.  
  14338.     /**
  14339.      * Sets the disabled property of all input elements within the 'Customize
  14340.      * Sync Preferences' screen. This is used to prohibit the user from changing
  14341.      * the inputs after confirming the customized sync preferences, or resetting
  14342.      * the state when re-showing the dialog.
  14343.      * @param {boolean} disabled True if controls should be set to disabled.
  14344.      * @private
  14345.      */
  14346.     setInputElementsDisabledState_: function(disabled) {
  14347.       var configureElements =
  14348.           $('customize-sync-preferences').querySelectorAll('input');
  14349.       for (var i = 0; i < configureElements.length; i++)
  14350.         configureElements[i].disabled = disabled;
  14351.       $('sync-select-datatypes').disabled = disabled;
  14352.  
  14353.       var self = this;
  14354.       this.animateDisableLink_($('customize-link'), disabled, function() {
  14355.         self.showCustomizePage_(null, true);
  14356.       });
  14357.     },
  14358.  
  14359.     /**
  14360.      * Animate a link being enabled/disabled. The link is hidden by animating
  14361.      * its opacity, but to ensure the user doesn't click it during that time,
  14362.      * its onclick handler is changed to null as well.
  14363.      * @param {HTMLElement} elt The anchor element to enable/disable.
  14364.      * @param {boolean} disabled True if the link should be disabled.
  14365.      * @param {function} enabledFunction The onclick handler when the link is
  14366.      *     enabled.
  14367.      * @private
  14368.      */
  14369.     animateDisableLink_: function(elt, disabled, enabledFunction) {
  14370.       if (disabled) {
  14371.         elt.classList.add('transparent');
  14372.         elt.onclick = null;
  14373.         elt.addEventListener('webkitTransitionEnd', function f(e) {
  14374.           if (e.propertyName != 'opacity')
  14375.             return;
  14376.           elt.removeEventListener('webkitTransitionEnd', f);
  14377.           elt.classList.remove('transparent');
  14378.           elt.hidden = true;
  14379.         });
  14380.       } else {
  14381.         elt.hidden = false;
  14382.         elt.onclick = enabledFunction;
  14383.       }
  14384.     },
  14385.  
  14386.     /**
  14387.      * Shows or hides the Sync data type checkboxes in the advanced
  14388.      * configuration screen.
  14389.      * @param {Object} args The configuration data used to show/hide UI.
  14390.      * @private
  14391.      */
  14392.     setChooseDataTypesCheckboxes_: function(args) {
  14393.       var datatypeSelect = $('sync-select-datatypes');
  14394.       datatypeSelect.selectedIndex = args.syncAllDataTypes ? 0 : 1;
  14395.  
  14396.       $('bookmarks-checkbox').checked = args.bookmarksSynced;
  14397.       $('preferences-checkbox').checked = args.preferencesSynced;
  14398.       $('themes-checkbox').checked = args.themesSynced;
  14399.  
  14400.       if (args.passwordsRegistered) {
  14401.         $('passwords-checkbox').checked = args.passwordsSynced;
  14402.         $('passwords-item').hidden = false;
  14403.       } else {
  14404.         $('passwords-item').hidden = true;
  14405.       }
  14406.       if (args.autofillRegistered) {
  14407.         $('autofill-checkbox').checked = args.autofillSynced;
  14408.         $('autofill-item').hidden = false;
  14409.       } else {
  14410.         $('autofill-item').hidden = true;
  14411.       }
  14412.       if (args.extensionsRegistered) {
  14413.         $('extensions-checkbox').checked = args.extensionsSynced;
  14414.         $('extensions-item').hidden = false;
  14415.       } else {
  14416.         $('extensions-item').hidden = true;
  14417.       }
  14418.       if (args.typedUrlsRegistered) {
  14419.         $('typed-urls-checkbox').checked = args.typedUrlsSynced;
  14420.         $('omnibox-item').hidden = false;
  14421.       } else {
  14422.         $('omnibox-item').hidden = true;
  14423.       }
  14424.       if (args.appsRegistered) {
  14425.         $('apps-checkbox').checked = args.appsSynced;
  14426.         $('apps-item').hidden = false;
  14427.       } else {
  14428.         $('apps-item').hidden = true;
  14429.       }
  14430.       if (args.sessionsRegistered) {
  14431.         $('sessions-checkbox').checked = args.sessionsSynced;
  14432.         $('sessions-item').hidden = false;
  14433.       } else {
  14434.         $('sessions-item').hidden = true;
  14435.       }
  14436.  
  14437.       this.setCheckboxesToKeepEverythingSynced_(args.syncAllDataTypes);
  14438.     },
  14439.  
  14440.     setEncryptionRadios_: function(args) {
  14441.       if (args.encryptAllData) {
  14442.         $('encrypt-all-option').checked = true;
  14443.         this.disableEncryptionRadioGroup_();
  14444.       } else {
  14445.         $('encrypt-sensitive-option').checked = true;
  14446.       }
  14447.  
  14448.       if (!args.encryptAllData && !args.usePassphrase) {
  14449.         $('basic-encryption-option').checked = true;
  14450.       } else {
  14451.         $('full-encryption-option').checked = true;
  14452.         $('full-encryption-option').disabled = true;
  14453.         $('basic-encryption-option').disabled = true;
  14454.       }
  14455.     },
  14456.  
  14457.     setPassphraseRadios_: function(args) {
  14458.       if (args.usePassphrase) {
  14459.         $('explicit-option').checked = true;
  14460.  
  14461.         // The passphrase, once set, cannot be unset, but we show a reset link.
  14462.         $('explicit-option').disabled = true;
  14463.         $('google-option').disabled = true;
  14464.         $('sync-custom-passphrase').hidden = true;
  14465.       } else {
  14466.         $('google-option').checked = true;
  14467.       }
  14468.     },
  14469.  
  14470.     setCheckboxesAndErrors_: function(args) {
  14471.       this.setChooseDataTypesCheckboxes_(args);
  14472.       this.setEncryptionRadios_(args);
  14473.       this.setPassphraseRadios_(args);
  14474.     },
  14475.  
  14476.     showConfigure_: function(args) {
  14477.       var datatypeSelect = $('sync-select-datatypes');
  14478.       var self = this;
  14479.       datatypeSelect.onchange = function() {
  14480.         var syncAll = this.selectedIndex == 0;
  14481.         self.setCheckboxesToKeepEverythingSynced_(syncAll);
  14482.       };
  14483.  
  14484.       this.resetPage_('sync-setup-configure');
  14485.       $('sync-setup-configure').hidden = false;
  14486.  
  14487.       // onsubmit is changed when submitting a passphrase. Reset it to its
  14488.       // default.
  14489.       $('choose-data-types-form').onsubmit = function() {
  14490.         self.sendConfiguration_();
  14491.         return false;
  14492.       };
  14493.  
  14494.       if (args) {
  14495.         this.setCheckboxesAndErrors_(args);
  14496.  
  14497.         this.useEncryptEverything_ = args.encryptAllData;
  14498.  
  14499.         // Whether to display the 'Sync everything' confirmation page or the
  14500.         // customize data types page.
  14501.         var syncAllDataTypes = args.syncAllDataTypes;
  14502.         this.usePassphrase_ = args.usePassphrase;
  14503.         this.keystoreEncryptionEnabled_ = args.keystoreEncryptionEnabled;
  14504.         if (args.showSyncEverythingPage == false || this.usePassphrase_ ||
  14505.             syncAllDataTypes == false || args.showPassphrase) {
  14506.           this.showCustomizePage_(args, syncAllDataTypes);
  14507.         } else {
  14508.           this.showSyncEverythingPage_();
  14509.         }
  14510.       }
  14511.     },
  14512.  
  14513.     showSpinner_: function() {
  14514.       this.resetPage_('sync-setup-spinner');
  14515.       $('sync-setup-spinner').hidden = false;
  14516.     },
  14517.  
  14518.     showTimeoutPage_: function() {
  14519.       this.resetPage_('sync-setup-timeout');
  14520.       $('sync-setup-timeout').hidden = false;
  14521.     },
  14522.  
  14523.     showSyncEverythingPage_: function() {
  14524.       $('confirm-sync-preferences').hidden = false;
  14525.       $('customize-sync-preferences').hidden = true;
  14526.  
  14527.       // Reset the selection to 'Sync everything'.
  14528.       $('sync-select-datatypes').selectedIndex = 0;
  14529.  
  14530.       // The default state is to sync everything.
  14531.       this.setCheckboxesToKeepEverythingSynced_(true);
  14532.  
  14533.       // Encrypt passwords is the default, but don't set it if the previously
  14534.       // synced account is already set to encrypt everything.
  14535.       if (!this.useEncryptEverything_)
  14536.         $('encrypt-sensitive-option').checked = true;
  14537.  
  14538.       // If the account is not synced with a custom passphrase, reset the
  14539.       // passphrase radio when switching to the 'Sync everything' page.
  14540.       if (!this.usePassphrase_) {
  14541.         $('google-option').checked = true;
  14542.         $('sync-custom-passphrase').hidden = true;
  14543.       }
  14544.  
  14545.       if (!this.useEncryptEverything_ && !this.usePassphrase_)
  14546.         $('basic-encryption-option').checked = true;
  14547.  
  14548.       $('confirm-everything-ok').focus();
  14549.     },
  14550.  
  14551.     /**
  14552.      * Reveals the UI for entering a custom passphrase during initial setup.
  14553.      * This happens if the user has previously enabled a custom passphrase on a
  14554.      * different machine.
  14555.      * @param {Array} args The args that contain the passphrase UI
  14556.      *     configuration.
  14557.      * @private
  14558.      */
  14559.     showPassphraseContainer_: function(args) {
  14560.       // Once we require a passphrase, we prevent the user from returning to
  14561.       // the Sync Everything pane.
  14562.       $('use-default-link').hidden = true;
  14563.       $('sync-custom-passphrase-container').hidden = true;
  14564.       $('sync-existing-passphrase-container').hidden = false;
  14565.  
  14566.       // Hide the selection options within the new encryption section when
  14567.       // prompting for a passphrase.
  14568.       $('sync-new-encryption-section-container').hidden = true;
  14569.  
  14570.       $('normal-body').hidden = true;
  14571.       $('google-passphrase-needed-body').hidden = true;
  14572.       // Display the correct prompt to the user depending on what type of
  14573.       // passphrase is needed.
  14574.       if (args.usePassphrase)
  14575.         $('normal-body').hidden = false;
  14576.       else
  14577.         $('google-passphrase-needed-body').hidden = false;
  14578.  
  14579.       $('passphrase-learn-more').hidden = false;
  14580.       // Warn the user about their incorrect passphrase if we need a passphrase
  14581.       // and the passphrase field is non-empty (meaning they tried to set it
  14582.       // previously but failed).
  14583.       $('incorrect-passphrase').hidden =
  14584.           !(args.usePassphrase && args.passphraseFailed);
  14585.  
  14586.       $('sync-passphrase-warning').hidden = false;
  14587.       $('passphrase').focus();
  14588.     },
  14589.  
  14590.     /** @private */
  14591.     showCustomizePage_: function(args, syncEverything) {
  14592.       $('confirm-sync-preferences').hidden = true;
  14593.       $('customize-sync-preferences').hidden = false;
  14594.  
  14595.       $('sync-custom-passphrase-container').hidden = false;
  14596.  
  14597.       if (this.keystoreEncryptionEnabled_) {
  14598.         $('customize-sync-encryption').hidden = true;
  14599.         $('sync-custom-passphrase-options').hidden = true;
  14600.         $('sync-new-encryption-section-container').hidden = false;
  14601.         $('customize-sync-encryption-new').hidden = false;
  14602.       } else {
  14603.         $('customize-sync-encryption').hidden = false;
  14604.         $('sync-custom-passphrase-options').hidden = false;
  14605.         $('customize-sync-encryption-new').hidden = true;
  14606.       }
  14607.  
  14608.       $('sync-existing-passphrase-container').hidden = true;
  14609.  
  14610.       // If the user has selected the 'Customize' page on initial set up, it's
  14611.       // likely he intends to change the data types. Select the
  14612.       // 'Choose data types' option in this case.
  14613.       var index = syncEverything ? 0 : 1;
  14614.       $('sync-select-datatypes').selectedIndex = index;
  14615.       this.setDataTypeCheckboxesEnabled_(!syncEverything);
  14616.  
  14617.       // The passphrase input may need to take over focus from the OK button, so
  14618.       // set focus before that logic.
  14619.       $('choose-datatypes-ok').focus();
  14620.  
  14621.       if (args && args.showPassphrase) {
  14622.         this.showPassphraseContainer_(args);
  14623.       } else {
  14624.         // We only show the 'Use Default' link if we're not prompting for an
  14625.         // existing passphrase.
  14626.         var self = this;
  14627.         this.animateDisableLink_($('use-default-link'), false, function() {
  14628.           self.showSyncEverythingPage_();
  14629.         });
  14630.       }
  14631.     },
  14632.  
  14633.     /**
  14634.      * Shows the appropriate sync setup page.
  14635.      * @param {string} page A page of the sync setup to show.
  14636.      * @param {object} args Data from the C++ to forward on to the right
  14637.      *     section.
  14638.      */
  14639.     showSyncSetupPage_: function(page, args) {
  14640.       this.setThrobbersVisible_(false);
  14641.  
  14642.       // Hide an existing visible overlay (ensuring the close button is not
  14643.       // hidden).
  14644.       var children = document.querySelectorAll(
  14645.           '#sync-setup-overlay > *:not(.close-button)');
  14646.       for (var i = 0; i < children.length; i++)
  14647.         children[i].hidden = true;
  14648.  
  14649.       this.setInputElementsDisabledState_(false);
  14650.  
  14651.       // If new passphrase bodies are present, overwrite the existing ones.
  14652.       if (args && args.enterPassphraseBody != undefined)
  14653.         $('normal-body').innerHTML = args.enterPassphraseBody;
  14654.       if (args && args.enterGooglePassphraseBody != undefined) {
  14655.         $('google-passphrase-needed-body').innerHTML =
  14656.             args.enterGooglePassphraseBody;
  14657.       }
  14658.       if (args && args.fullEncryptionBody != undefined)
  14659.         $('full-encryption-body').innerHTML = args.fullEncryptionBody;
  14660.  
  14661.       // NOTE: Because both showGaiaLogin_() and showConfigure_() change the
  14662.       // focus, we need to ensure that the overlay container and dialog aren't
  14663.       // [hidden] (as trying to focus() nodes inside of a [hidden] DOM section
  14664.       // doesn't work).
  14665.       if (page == 'done')
  14666.         this.closeOverlay_();
  14667.       else
  14668.         this.showOverlay_();
  14669.  
  14670.       if (page == 'login')
  14671.         this.showGaiaLogin_(args);
  14672.       else if (page == 'configure' || page == 'passphrase')
  14673.         this.showConfigure_(args);
  14674.       else if (page == 'spinner')
  14675.         this.showSpinner_();
  14676.       else if (page == 'timeout')
  14677.         this.showTimeoutPage_();
  14678.     },
  14679.  
  14680.     /**
  14681.      * Changes the visibility of throbbers on this page.
  14682.      * @param {boolean} visible Whether or not to set all throbber nodes
  14683.      *     visible.
  14684.      */
  14685.     setThrobbersVisible_: function(visible) {
  14686.       var throbbers = document.getElementsByClassName('throbber');
  14687.       for (var i = 0; i < throbbers.length; i++)
  14688.         throbbers[i].style.visibility = visible ? 'visible' : 'hidden';
  14689.     },
  14690.  
  14691.     /**
  14692.      * Set the appropriate focus on the GAIA login section of the overlay.
  14693.      * @private
  14694.      */
  14695.     loginSetFocus_: function() {
  14696.       var email = this.getLoginEmail_();
  14697.       if (email && !email.value) {
  14698.         email.focus();
  14699.         return;
  14700.       }
  14701.  
  14702.       var passwd = this.getLoginPasswd_();
  14703.       if (passwd)
  14704.         passwd.focus();
  14705.     },
  14706.  
  14707.     /**
  14708.      * Get the login email text input DOM element.
  14709.      * @return {DOMElement} The login email text input.
  14710.      * @private
  14711.      */
  14712.     getLoginEmail_: function() {
  14713.       return $('gaia-email');
  14714.     },
  14715.  
  14716.     /**
  14717.      * Get the login password text input DOM element.
  14718.      * @return {DOMElement} The login password text input.
  14719.      * @private
  14720.      */
  14721.     getLoginPasswd_: function() {
  14722.       return $('gaia-passwd');
  14723.     },
  14724.  
  14725.     /**
  14726.      * Get the sign in button DOM element.
  14727.      * @return {DOMElement} The sign in button.
  14728.      * @private
  14729.      */
  14730.     getSignInButton_: function() {
  14731.       return $('sign-in');
  14732.     },
  14733.  
  14734.     showAccessCodeRequired_: function() {
  14735.       this.allowEmptyPassword_ = true;
  14736.  
  14737.       $('password-row').hidden = true;
  14738.       $('email-row').hidden = true;
  14739.       $('otp-input-row').hidden = true;
  14740.  
  14741.       $('access-code-input-row').hidden = false;
  14742.       $('access-code').disabled = false;
  14743.       $('access-code').focus();
  14744.     },
  14745.  
  14746.     showOtpRequired_: function() {
  14747.       this.allowEmptyPassword_ = true;
  14748.  
  14749.       $('password-row').hidden = true;
  14750.       $('email-row').hidden = true;
  14751.       $('access-code-input-row').hidden = true;
  14752.  
  14753.       $('otp-input-row').hidden = false;
  14754.       $('otp').disabled = false;
  14755.       $('otp').focus();
  14756.     },
  14757.  
  14758.     showCaptcha_: function(args) {
  14759.       this.allowEmptyPassword_ = args.hideEmailAndPassword;
  14760.       this.captchaChallengeActive_ = true;
  14761.  
  14762.       if (args.hideEmailAndPassword) {
  14763.         $('password-row').hidden = true;
  14764.         $('email-row').hidden = true;
  14765.         $('create-account-div').hidden = true;
  14766.       } else {
  14767.         // The captcha takes up lots of space, so make room.
  14768.         $('top-blurb-error').hidden = true;
  14769.         $('create-account-div').hidden = true;
  14770.         $('gaia-email').disabled = true;
  14771.         $('gaia-passwd').disabled = false;
  14772.       }
  14773.  
  14774.       // It's showtime for the captcha now.
  14775.       $('captcha-div').hidden = false;
  14776.       $('captcha-value').disabled = false;
  14777.       $('captcha-wrapper').style.backgroundImage = url(args.captchaUrl);
  14778.     },
  14779.  
  14780.     /**
  14781.      * Reset the state of all descendant elements of a root element to their
  14782.      * initial state.
  14783.      * The initial state is specified by adding a class to the descendant
  14784.      * element in sync_setup_overlay.html.
  14785.      * @param {HTMLElement} pageElementId The root page element id.
  14786.      * @private
  14787.      */
  14788.     resetPage_: function(pageElementId) {
  14789.       var page = $(pageElementId);
  14790.       var forEach = function(arr, fn) {
  14791.         var length = arr.length;
  14792.         for (var i = 0; i < length; i++) {
  14793.           fn(arr[i]);
  14794.         }
  14795.       };
  14796.  
  14797.       forEach(page.getElementsByClassName('reset-hidden'),
  14798.           function(elt) { elt.hidden = true; });
  14799.       forEach(page.getElementsByClassName('reset-shown'),
  14800.           function(elt) { elt.hidden = false; });
  14801.       forEach(page.getElementsByClassName('reset-disabled'),
  14802.           function(elt) { elt.disabled = true; });
  14803.       forEach(page.getElementsByClassName('reset-enabled'),
  14804.           function(elt) { elt.disabled = false; });
  14805.       forEach(page.getElementsByClassName('reset-value'),
  14806.           function(elt) { elt.value = ''; });
  14807.       forEach(page.getElementsByClassName('reset-opaque'),
  14808.           function(elt) { elt.classList.remove('transparent'); });
  14809.     },
  14810.  
  14811.     showGaiaLogin_: function(args) {
  14812.       var oldAccessCodeValue = $('access-code').value;
  14813.       this.resetPage_('sync-setup-login');
  14814.       $('sync-setup-login').hidden = false;
  14815.       this.allowEmptyPassword_ = false;
  14816.       this.captchaChallengeActive_ = false;
  14817.       this.lastEmailAddress_ = args.lastEmailAddress;
  14818.  
  14819.       var f = $('gaia-login-form');
  14820.       var email = $('gaia-email');
  14821.       var passwd = $('gaia-passwd');
  14822.       if (f) {
  14823.         if (args.user != undefined) {
  14824.           if (email.value != args.user)
  14825.             passwd.value = ''; // Reset the password field
  14826.           email.value = args.user;
  14827.         }
  14828.  
  14829.         if (!args.editableUser) {
  14830.           $('email-row').hidden = true;
  14831.           var span = $('email-readonly');
  14832.           span.textContent = email.value;
  14833.           $('email-readonly-row').hidden = false;
  14834.           $('create-account-div').hidden = true;
  14835.         }
  14836.  
  14837.         f.accessCode.disabled = true;
  14838.         f.otp.disabled = true;
  14839.       }
  14840.  
  14841.       if (1 == args.error) {
  14842.         if (oldAccessCodeValue) {
  14843.           $('errormsg-0-access-code').hidden = false;
  14844.           this.showAccessCodeRequired_();
  14845.         } else {
  14846.           $('errormsg-1-password').hidden = (args.errorMessage != undefined);
  14847.         }
  14848.         this.setBlurbError_(args.errorMessage);
  14849.       } else if (3 == args.error) {
  14850.         $('errormsg-0-connection').hidden = false;
  14851.         this.setBlurbError_(args.errorMessage);
  14852.       } else if (4 == args.error) {
  14853.         this.showCaptcha_(args);
  14854.       } else if (7 == args.error) {
  14855.         this.setBlurbError_(loadTimeData.getString('serviceUnavailableError'));
  14856.       } else if (8 == args.error) {
  14857.         if (args.askForOtp) {
  14858.           this.showOtpRequired_();
  14859.         } else {
  14860.           if (oldAccessCodeValue)
  14861.             $('errormsg-0-access-code').hidden = false;
  14862.           this.showAccessCodeRequired_();
  14863.         }
  14864.       } else if (args.errorMessage) {
  14865.         this.setBlurbError_(args.errorMessage);
  14866.       }
  14867.  
  14868.       if (args.fatalError) {
  14869.         $('errormsg-fatal').hidden = false;
  14870.         $('sign-in').disabled = true;
  14871.         return;
  14872.       }
  14873.  
  14874.       $('sign-in').disabled = false;
  14875.       $('sign-in').value = loadTimeData.getString('signin');
  14876.       this.loginSetFocus_();
  14877.     },
  14878.  
  14879.     resetErrorVisibility_: function() {
  14880.       $('errormsg-0-email').hidden = true;
  14881.       $('errormsg-0-password').hidden = true;
  14882.       $('errormsg-1-password').hidden = true;
  14883.       $('errormsg-different-email').hidden = true;
  14884.       $('errormsg-0-connection').hidden = true;
  14885.       $('errormsg-0-access-code').hidden = true;
  14886.       $('errormsg-0-otp').hidden = true;
  14887.     },
  14888.  
  14889.     setBlurbError_: function(errorMessage) {
  14890.       if (this.captchaChallengeActive_)
  14891.         return;  // No blurb in captcha challenge mode.
  14892.  
  14893.       if (errorMessage) {
  14894.         $('error-signing-in').hidden = true;
  14895.         $('error-custom').hidden = false;
  14896.         $('error-custom').textContent = errorMessage;
  14897.       } else {
  14898.         $('error-signing-in').hidden = false;
  14899.         $('error-custom').hidden = true;
  14900.       }
  14901.  
  14902.       $('top-blurb-error').hidden = false;
  14903.       $('gaia-email').disabled = false;
  14904.       $('gaia-passwd').disabled = false;
  14905.     },
  14906.  
  14907.     matchesASPRegex_: function(toMatch) {
  14908.       var noSpaces = /[a-z]{16}/;
  14909.       var withSpaces = /([a-z]{4}\s){3}[a-z]{4}/;
  14910.       if (toMatch.match(noSpaces) || toMatch.match(withSpaces))
  14911.         return true;
  14912.       return false;
  14913.     },
  14914.  
  14915.     setErrorVisibility_: function() {
  14916.       var errormsgDifferentEmail = $('errormsg-different-email');
  14917.       var isErrormsgDifferentEmailHidden = errormsgDifferentEmail.hidden;
  14918.       this.resetErrorVisibility_();
  14919.       var f = $('gaia-login-form');
  14920.       var email = $('gaia-email');
  14921.       var passwd = $('gaia-passwd');
  14922.       if (!email.value) {
  14923.         $('errormsg-0-email').hidden = false;
  14924.         this.setBlurbError_();
  14925.         return false;
  14926.       }
  14927.       // If email is different from last email, and we have not already warned
  14928.       // the user, tell them now.  Otherwise proceed as usual. When comparing
  14929.       // email ids, use @gmail.com as the domain if not provided.
  14930.       function normalized_email(id) {
  14931.         return ((id.indexOf('@') != -1) ? id : id + '@gmail.com');
  14932.       }
  14933.       if (this.lastEmailAddress_.length > 0 &&
  14934.           normalized_email(email.value) !=
  14935.               normalized_email(this.lastEmailAddress_) &&
  14936.           isErrormsgDifferentEmailHidden) {
  14937.         errormsgDifferentEmail.hidden = false;
  14938.         return false;
  14939.       }
  14940.       // Don't enforce password being non-blank when checking access code (it
  14941.       // will have been cleared when the page was displayed).
  14942.       if (!this.allowEmptyPassword_ && !passwd.value) {
  14943.         $('errormsg-0-password').hidden = false;
  14944.         this.setBlurbError_();
  14945.         return false;
  14946.       }
  14947.  
  14948.       if (!f.accessCode.disabled && !f.accessCode.value) {
  14949.         $('errormsg-0-access-code').hidden = false;
  14950.         return false;
  14951.       }
  14952.  
  14953.       if (f.accessCode.disabled && this.matchesASPRegex_(passwd.value) &&
  14954.           $('asp-warning-div').hidden) {
  14955.         $('asp-warning-div').hidden = false;
  14956.         $('gaia-passwd').value = '';
  14957.         return false;
  14958.       }
  14959.  
  14960.       if (!f.otp.disabled && !f.otp.value) {
  14961.         $('errormsg-0-otp').hidden = false;
  14962.         return false;
  14963.       }
  14964.  
  14965.       return true;
  14966.     },
  14967.  
  14968.     sendCredentialsAndClose_: function() {
  14969.       if (!this.setErrorVisibility_()) {
  14970.         return false;
  14971.       }
  14972.  
  14973.       $('gaia-email').disabled = true;
  14974.       $('gaia-passwd').disabled = true;
  14975.       $('captcha-value').disabled = true;
  14976.       $('access-code').disabled = true;
  14977.       $('otp').disabled = true;
  14978.  
  14979.       this.setThrobbersVisible_(true);
  14980.  
  14981.       var f = $('gaia-login-form');
  14982.       var email = $('gaia-email');
  14983.       var passwd = $('gaia-passwd');
  14984.       var result = JSON.stringify({'user': email.value,
  14985.         'pass': passwd.value,
  14986.         'captcha': f.captchaValue.value,
  14987.         'otp': f.otp.value,
  14988.         'accessCode': f.accessCode.value
  14989.       });
  14990.       $('sign-in').disabled = true;
  14991.       chrome.send('SyncSetupSubmitAuth', [result]);
  14992.     },
  14993.  
  14994.     showSuccessAndClose_: function() {
  14995.       $('sign-in').value = loadTimeData.getString('loginSuccess');
  14996.       setTimeout(this.closeOverlay_, 1600);
  14997.     },
  14998.  
  14999.     showSuccessAndSettingUp_: function() {
  15000.       $('sign-in').value = loadTimeData.getString('settingUp');
  15001.       this.setThrobbersVisible_(true);
  15002.       $('top-blurb-error').hidden = true;
  15003.     },
  15004.  
  15005.     /**
  15006.      * Displays the stop syncing dialog.
  15007.      * @private
  15008.      */
  15009.     showStopSyncingUI_: function() {
  15010.       // Hide any visible children of the overlay.
  15011.       var overlay = $('sync-setup-overlay');
  15012.       for (var i = 0; i < overlay.children.length; i++)
  15013.         overlay.children[i].hidden = true;
  15014.  
  15015.       // Bypass OptionsPage.navigateToPage because it will call didShowPage
  15016.       // which will set its own visible page, based on the flow state.
  15017.       this.visible = true;
  15018.  
  15019.       $('sync-setup-stop-syncing').hidden = false;
  15020.       $('stop-syncing-cancel').focus();
  15021.     },
  15022.  
  15023.     /**
  15024.      * Steps into the appropriate Sync Setup error UI.
  15025.      * @private
  15026.      */
  15027.     showErrorUI_: function() {
  15028.       chrome.send('SyncSetupShowErrorUI');
  15029.     },
  15030.  
  15031.     /**
  15032.      * Determines the appropriate page to show in the Sync Setup UI based on
  15033.      * the state of the Sync backend.
  15034.      * @private
  15035.      */
  15036.     showSetupUI_: function() {
  15037.       chrome.send('SyncSetupShowSetupUI');
  15038.     },
  15039.  
  15040.     /**
  15041.      * Shows advanced configuration UI, skipping the login dialog.
  15042.      * @private
  15043.      */
  15044.     showSetupUIWithoutLogin_: function() {
  15045.       chrome.send('SyncSetupShowSetupUIWithoutLogin');
  15046.     },
  15047.  
  15048.     /**
  15049.      * Forces user to sign out of Chrome for Chrome OS.
  15050.      * @private
  15051.      */
  15052.     doSignOutOnAuthError_: function() {
  15053.       chrome.send('SyncSetupDoSignOutOnAuthError');
  15054.     },
  15055.  
  15056.     /**
  15057.      * Hides the outer elements of the login UI. This is used by the sync promo
  15058.      * to customize the look of the login box.
  15059.      */
  15060.     hideOuterLoginUI_: function() {
  15061.       $('sync-setup-overlay-title').hidden = true;
  15062.       $('sync-setup-cancel').hidden = true;
  15063.     }
  15064.   };
  15065.  
  15066.   // These get methods should only be called by the WebUI tests.
  15067.   SyncSetupOverlay.getLoginEmail = function() {
  15068.     return SyncSetupOverlay.getInstance().getLoginEmail_();
  15069.   };
  15070.  
  15071.   SyncSetupOverlay.getLoginPasswd = function() {
  15072.     return SyncSetupOverlay.getInstance().getLoginPasswd_();
  15073.   };
  15074.  
  15075.   SyncSetupOverlay.getSignInButton = function() {
  15076.     return SyncSetupOverlay.getInstance().getSignInButton_();
  15077.   };
  15078.  
  15079.   // These methods are for general consumption.
  15080.   SyncSetupOverlay.showErrorUI = function() {
  15081.     SyncSetupOverlay.getInstance().showErrorUI_();
  15082.   };
  15083.  
  15084.   SyncSetupOverlay.showSetupUI = function() {
  15085.     SyncSetupOverlay.getInstance().showSetupUI_();
  15086.   };
  15087.  
  15088.   SyncSetupOverlay.showSetupUIWithoutLogin = function() {
  15089.     SyncSetupOverlay.getInstance().showSetupUIWithoutLogin_();
  15090.   };
  15091.  
  15092.   SyncSetupOverlay.doSignOutOnAuthError = function() {
  15093.     SyncSetupOverlay.getInstance().doSignOutOnAuthError_();
  15094.   };
  15095.  
  15096.   SyncSetupOverlay.showSyncSetupPage = function(page, args) {
  15097.     SyncSetupOverlay.getInstance().showSyncSetupPage_(page, args);
  15098.   };
  15099.  
  15100.   SyncSetupOverlay.showSuccessAndClose = function() {
  15101.     SyncSetupOverlay.getInstance().showSuccessAndClose_();
  15102.   };
  15103.  
  15104.   SyncSetupOverlay.showSuccessAndSettingUp = function() {
  15105.     SyncSetupOverlay.getInstance().showSuccessAndSettingUp_();
  15106.   };
  15107.  
  15108.   SyncSetupOverlay.showStopSyncingUI = function() {
  15109.     SyncSetupOverlay.getInstance().showStopSyncingUI_();
  15110.   };
  15111.  
  15112.   // Export
  15113.   return {
  15114.     SyncSetupOverlay: SyncSetupOverlay
  15115.   };
  15116. });
  15117.  
  15118. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  15119. // Use of this source code is governed by a BSD-style license that can be
  15120. // found in the LICENSE file.
  15121.  
  15122. /**
  15123.  * @fileoverview A collection of utility methods for UberPage and its contained
  15124.  *     pages.
  15125.  */
  15126.  
  15127. cr.define('uber', function() {
  15128.  
  15129.   /**
  15130.    * Fixed position header elements on the page to be shifted by handleScroll.
  15131.    * @type {NodeList}
  15132.    */
  15133.   var headerElements;
  15134.  
  15135.   /**
  15136.    * This should be called by uber content pages when DOM content has loaded.
  15137.    */
  15138.   function onContentFrameLoaded() {
  15139.     headerElements = document.getElementsByTagName('header');
  15140.     document.addEventListener('scroll', handleScroll);
  15141.  
  15142.     // Trigger the scroll handler to tell the navigation if our page started
  15143.     // with some scroll (happens when you use tab restore).
  15144.     handleScroll();
  15145.  
  15146.     window.addEventListener('message', handleWindowMessage);
  15147.   }
  15148.  
  15149.   /**
  15150.    * Handles scroll events on the document. This adjusts the position of all
  15151.    * headers and updates the parent frame when the page is scrolled.
  15152.    * @private
  15153.    */
  15154.   function handleScroll() {
  15155.     var offset = document.body.scrollLeft * -1;
  15156.     for (var i = 0; i < headerElements.length; i++)
  15157.       headerElements[i].style.webkitTransform = 'translateX(' + offset + 'px)';
  15158.  
  15159.     invokeMethodOnParent('adjustToScroll', document.body.scrollLeft);
  15160.   };
  15161.  
  15162.   /**
  15163.    * Handles 'message' events on window.
  15164.    * @param {Event} e The message event.
  15165.    */
  15166.   function handleWindowMessage(e) {
  15167.     if (e.data.method === 'frameSelected')
  15168.       handleFrameSelected();
  15169.     else if (e.data.method === 'mouseWheel')
  15170.       handleMouseWheel(e.data.params);
  15171.   }
  15172.  
  15173.   /**
  15174.    * This is called when a user selects this frame via the navigation bar
  15175.    * frame (and is triggered via postMessage() from the uber page).
  15176.    * @private
  15177.    */
  15178.   function handleFrameSelected() {
  15179.     document.body.scrollLeft = 0;
  15180.   }
  15181.  
  15182.   /**
  15183.    * Called when a user mouse wheels (or trackpad scrolls) over the nav frame.
  15184.    * The wheel event is forwarded here and we scroll the body.
  15185.    * There's no way to figure out the actual scroll amount for a given delta.
  15186.    * It differs for every platform and even initWebKitWheelEvent takes a
  15187.    * pixel amount instead of a wheel delta. So we just choose something
  15188.    * reasonable and hope no one notices the difference.
  15189.    * @param {Object} params A structure that holds wheel deltas in X and Y.
  15190.    */
  15191.   function handleMouseWheel(params) {
  15192.     document.body.scrollTop -= params.deltaY * 49 / 120;
  15193.     document.body.scrollLeft -= params.deltaX * 49 / 120;
  15194.   }
  15195.  
  15196.   /**
  15197.    * Invokes a method on the parent window (UberPage). This is a convenience
  15198.    * method for API calls into the uber page.
  15199.    * @param {String} method The name of the method to invoke.
  15200.    * @param {Object=} opt_params Optional property bag of parameters to pass to
  15201.    *     the invoked method.
  15202.    * @private
  15203.    */
  15204.   function invokeMethodOnParent(method, opt_params) {
  15205.     if (window.location == window.parent.location)
  15206.       return;
  15207.  
  15208.     invokeMethodOnWindow(window.parent, method, opt_params, 'chrome://chrome');
  15209.   }
  15210.  
  15211.   /**
  15212.    * Invokes a method on the target window.
  15213.    * @param {String} method The name of the method to invoke.
  15214.    * @param {Object=} opt_params Optional property bag of parameters to pass to
  15215.    *     the invoked method.
  15216.    * @param {String=} opt_url The origin of the target window.
  15217.    * @private
  15218.    */
  15219.   function invokeMethodOnWindow(targetWindow, method, opt_params, opt_url) {
  15220.     var data = {method: method, params: opt_params};
  15221.     targetWindow.postMessage(data, opt_url ? opt_url : '*');
  15222.   }
  15223.  
  15224.   return {
  15225.     invokeMethodOnParent: invokeMethodOnParent,
  15226.     invokeMethodOnWindow: invokeMethodOnWindow,
  15227.     onContentFrameLoaded: onContentFrameLoaded,
  15228.   };
  15229. });
  15230.  
  15231. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  15232. // Use of this source code is governed by a BSD-style license that can be
  15233. // found in the LICENSE file.
  15234.  
  15235. var AddLanguageOverlay = options.AddLanguageOverlay;
  15236. var AlertOverlay = options.AlertOverlay;
  15237. var AutofillEditAddressOverlay = options.AutofillEditAddressOverlay;
  15238. var AutofillEditCreditCardOverlay = options.AutofillEditCreditCardOverlay;
  15239. var AutofillOptions = options.AutofillOptions;
  15240. var BrowserOptions = options.BrowserOptions;
  15241. var ClearBrowserDataOverlay = options.ClearBrowserDataOverlay;
  15242. var ConfirmDialog = options.ConfirmDialog;
  15243. var ContentSettingsExceptionsArea =
  15244.     options.contentSettings.ContentSettingsExceptionsArea;
  15245. var ContentSettings = options.ContentSettings;
  15246. var CookiesView = options.CookiesView;
  15247. var EditDictionaryOverlay = cr.IsMac ? null : options.EditDictionaryOverlay;
  15248. var FactoryResetOverlay = options.FactoryResetOverlay;
  15249. var ManagedUserSettings = options.ManagedUserSettings;
  15250. var FontSettings = options.FontSettings;
  15251. var HandlerOptions = options.HandlerOptions;
  15252. var HomePageOverlay = options.HomePageOverlay;
  15253. var ImportDataOverlay = options.ImportDataOverlay;
  15254. var LanguageOptions = options.LanguageOptions;
  15255. var ManageProfileOverlay = options.ManageProfileOverlay;
  15256. var MediaGalleriesManager = options.MediaGalleriesManager;
  15257. var OptionsFocusManager = options.OptionsFocusManager;
  15258. var OptionsPage = options.OptionsPage;
  15259. var PasswordManager = options.PasswordManager;
  15260. var Preferences = options.Preferences;
  15261. var PreferredNetworks = options.PreferredNetworks;
  15262. var SearchEngineManager = options.SearchEngineManager;
  15263. var SearchPage = options.SearchPage;
  15264. var StartupOverlay = options.StartupOverlay;
  15265. var SyncSetupOverlay = options.SyncSetupOverlay;
  15266.  
  15267. /**
  15268.  * DOMContentLoaded handler, sets up the page.
  15269.  */
  15270. function load() {
  15271.   // Decorate the existing elements in the document.
  15272.   cr.ui.decorate('input[pref][type=checkbox]', options.PrefCheckbox);
  15273.   cr.ui.decorate('input[pref][type=number]', options.PrefNumber);
  15274.   cr.ui.decorate('input[pref][type=radio]', options.PrefRadio);
  15275.   cr.ui.decorate('input[pref][type=range]', options.PrefRange);
  15276.   cr.ui.decorate('select[pref]', options.PrefSelect);
  15277.   cr.ui.decorate('input[pref][type=text]', options.PrefTextField);
  15278.   cr.ui.decorate('input[pref][type=url]', options.PrefTextField);
  15279.   cr.ui.decorate('button[pref]', options.PrefButton);
  15280.   cr.ui.decorate('#content-settings-page input[type=radio]:not(.handler-radio)',
  15281.       options.ContentSettingsRadio);
  15282.   cr.ui.decorate('#content-settings-page input[type=radio].handler-radio',
  15283.       options.HandlersEnabledRadio);
  15284.   cr.ui.decorate('span.controlled-setting-indicator',
  15285.       options.ControlledSettingIndicator);
  15286.  
  15287.   // Top level pages.
  15288.   OptionsPage.register(SearchPage.getInstance());
  15289.   OptionsPage.register(BrowserOptions.getInstance());
  15290.  
  15291.   // Overlays.
  15292.   OptionsPage.registerOverlay(AddLanguageOverlay.getInstance(),
  15293.                               LanguageOptions.getInstance());
  15294.   OptionsPage.registerOverlay(AlertOverlay.getInstance());
  15295.   OptionsPage.registerOverlay(AutofillEditAddressOverlay.getInstance(),
  15296.                               AutofillOptions.getInstance());
  15297.   OptionsPage.registerOverlay(AutofillEditCreditCardOverlay.getInstance(),
  15298.                               AutofillOptions.getInstance());
  15299.   OptionsPage.registerOverlay(AutofillOptions.getInstance(),
  15300.                               BrowserOptions.getInstance(),
  15301.                               [$('autofill-settings')]);
  15302.   OptionsPage.registerOverlay(ClearBrowserDataOverlay.getInstance(),
  15303.                               BrowserOptions.getInstance(),
  15304.                               [$('privacyClearDataButton')]);
  15305.   OptionsPage.registerOverlay(
  15306.       new ConfirmDialog(
  15307.           'doNotTrackConfirm',
  15308.           loadTimeData.getString('doNotTrackConfirmOverlayTabTitle'),
  15309.           'do-not-track-confirm-overlay',
  15310.           $('do-not-track-confirm-ok'),
  15311.           $('do-not-track-confirm-cancel'),
  15312.           $('do-not-track-enabled').pref,
  15313.           $('do-not-track-enabled').metric),
  15314.       BrowserOptions.getInstance());
  15315.   OptionsPage.registerOverlay(
  15316.       new ConfirmDialog(
  15317.           'instantConfirm',
  15318.           loadTimeData.getString('instantConfirmOverlayTabTitle'),
  15319.           'instantConfirmOverlay',
  15320.           $('instantConfirmOk'),
  15321.           $('instantConfirmCancel'),
  15322.           $('instant-enabled-control').pref,
  15323.           $('instant-enabled-control').metric,
  15324.           'instant.confirm_dialog_shown'),
  15325.       BrowserOptions.getInstance());
  15326.   // 'spelling-enabled-control' element is only present on Chrome branded
  15327.   // builds.
  15328.   if ($('spelling-enabled-control')) {
  15329.     OptionsPage.registerOverlay(
  15330.         new ConfirmDialog(
  15331.             'spellingConfirm',
  15332.             loadTimeData.getString('spellingConfirmOverlayTabTitle'),
  15333.             'spelling-confirm-overlay',
  15334.             $('spelling-confirm-ok'),
  15335.             $('spelling-confirm-cancel'),
  15336.             $('spelling-enabled-control').pref,
  15337.             $('spelling-enabled-control').metric,
  15338.             'spellcheck.confirm_dialog_shown'),
  15339.         BrowserOptions.getInstance());
  15340.   }
  15341.   OptionsPage.registerOverlay(ContentSettings.getInstance(),
  15342.                               BrowserOptions.getInstance(),
  15343.                               [$('privacyContentSettingsButton')]);
  15344.   OptionsPage.registerOverlay(ContentSettingsExceptionsArea.getInstance(),
  15345.                               ContentSettings.getInstance());
  15346.   OptionsPage.registerOverlay(CookiesView.getInstance(),
  15347.                               ContentSettings.getInstance(),
  15348.                               [$('privacyContentSettingsButton'),
  15349.                                $('show-cookies-button')]);
  15350.   if (!cr.isMac) {
  15351.     OptionsPage.registerOverlay(EditDictionaryOverlay.getInstance(),
  15352.                                 LanguageOptions.getInstance(),
  15353.                                 [$('edit-dictionary-button')]);
  15354.   }
  15355.   OptionsPage.registerOverlay(FontSettings.getInstance(),
  15356.                               BrowserOptions.getInstance(),
  15357.                               [$('fontSettingsCustomizeFontsButton')]);
  15358.   if (HandlerOptions && $('manage-handlers-button')) {
  15359.     OptionsPage.registerOverlay(HandlerOptions.getInstance(),
  15360.                                 ContentSettings.getInstance(),
  15361.                                 [$('manage-handlers-button')]);
  15362.   }
  15363.   OptionsPage.registerOverlay(HomePageOverlay.getInstance(),
  15364.                               BrowserOptions.getInstance(),
  15365.                               [$('change-home-page')]);
  15366.   OptionsPage.registerOverlay(ImportDataOverlay.getInstance(),
  15367.                               BrowserOptions.getInstance());
  15368.   OptionsPage.registerOverlay(LanguageOptions.getInstance(),
  15369.                               BrowserOptions.getInstance(),
  15370.                               [$('language-button')]);
  15371.   OptionsPage.registerOverlay(ManageProfileOverlay.getInstance(),
  15372.                               BrowserOptions.getInstance());
  15373.   if (loadTimeData.getBoolean('managedUsersEnabled')) {
  15374.     OptionsPage.registerOverlay(ManagedUserSettings.getInstance(),
  15375.                                 BrowserOptions.getInstance(),
  15376.                                 []);
  15377.   }
  15378.   OptionsPage.registerOverlay(MediaGalleriesManager.getInstance(),
  15379.                               ContentSettings.getInstance(),
  15380.                               [$('manage-galleries-button')]);
  15381.   OptionsPage.registerOverlay(PasswordManager.getInstance(),
  15382.                               BrowserOptions.getInstance(),
  15383.                               [$('manage-passwords')]);
  15384.   OptionsPage.registerOverlay(SearchEngineManager.getInstance(),
  15385.                               BrowserOptions.getInstance(),
  15386.                               [$('manage-default-search-engines')]);
  15387.   OptionsPage.registerOverlay(StartupOverlay.getInstance(),
  15388.                               BrowserOptions.getInstance());
  15389.   OptionsPage.registerOverlay(SyncSetupOverlay.getInstance(),
  15390.                               BrowserOptions.getInstance());
  15391.   if (cr.isChromeOS) {
  15392.     OptionsPage.registerOverlay(AccountsOptions.getInstance(),
  15393.                                 BrowserOptions.getInstance(),
  15394.                                 [$('manage-accounts-button')]);
  15395.     OptionsPage.registerOverlay(BluetoothOptions.getInstance(),
  15396.                                 BrowserOptions.getInstance(),
  15397.                                 [$('bluetooth-add-device')]);
  15398.     OptionsPage.registerOverlay(BluetoothPairing.getInstance(),
  15399.                                 BrowserOptions.getInstance());
  15400.     OptionsPage.registerOverlay(FactoryResetOverlay.getInstance(),
  15401.                                 BrowserOptions.getInstance(),
  15402.                                 [$('factory-reset-restart')]);
  15403.     OptionsPage.registerOverlay(ChangePictureOptions.getInstance(),
  15404.                                 BrowserOptions.getInstance(),
  15405.                                 [$('account-picture')]);
  15406.     OptionsPage.registerOverlay(DetailsInternetPage.getInstance(),
  15407.                                 BrowserOptions.getInstance());
  15408.     OptionsPage.registerOverlay(DisplayOptions.getInstance(),
  15409.                                 BrowserOptions.getInstance(),
  15410.                                 [$('display-options')]);
  15411.     OptionsPage.registerOverlay(KeyboardOverlay.getInstance(),
  15412.                                 BrowserOptions.getInstance(),
  15413.                                 [$('keyboard-settings-button')]);
  15414.     OptionsPage.registerOverlay(PointerOverlay.getInstance(),
  15415.                                 BrowserOptions.getInstance(),
  15416.                                 [$('pointer-settings-button')]);
  15417.     OptionsPage.registerOverlay(PreferredNetworks.getInstance(),
  15418.                                 BrowserOptions.getInstance());
  15419.     OptionsPage.registerOverlay(
  15420.         new OptionsPage('languageChewing',
  15421.                         loadTimeData.getString('languageChewingPageTabTitle'),
  15422.                         'languageChewingPage'),
  15423.         LanguageOptions.getInstance());
  15424.     OptionsPage.registerOverlay(
  15425.         new OptionsPage('languageHangul',
  15426.                         loadTimeData.getString('languageHangulPageTabTitle'),
  15427.                         'languageHangulPage'),
  15428.         LanguageOptions.getInstance());
  15429.     OptionsPage.registerOverlay(
  15430.         new OptionsPage('languageMozc',
  15431.                         loadTimeData.getString('languageMozcPageTabTitle'),
  15432.                         'languageMozcPage'),
  15433.         LanguageOptions.getInstance());
  15434.     OptionsPage.registerOverlay(
  15435.         new OptionsPage('languagePinyin',
  15436.                         loadTimeData.getString('languagePinyinPageTabTitle'),
  15437.                         'languagePinyinPage'),
  15438.         LanguageOptions.getInstance());
  15439.   }
  15440.  
  15441.   if (!cr.isWindows && !cr.isMac) {
  15442.     OptionsPage.registerOverlay(CertificateBackupOverlay.getInstance(),
  15443.                                 CertificateManager.getInstance());
  15444.     OptionsPage.registerOverlay(CertificateEditCaTrustOverlay.getInstance(),
  15445.                                 CertificateManager.getInstance());
  15446.     OptionsPage.registerOverlay(CertificateImportErrorOverlay.getInstance(),
  15447.                                 CertificateManager.getInstance());
  15448.     OptionsPage.registerOverlay(CertificateManager.getInstance(),
  15449.                                 BrowserOptions.getInstance(),
  15450.                                 [$('certificatesManageButton')]);
  15451.     OptionsPage.registerOverlay(CertificateRestoreOverlay.getInstance(),
  15452.                                 CertificateManager.getInstance());
  15453.   }
  15454.  
  15455.   OptionsFocusManager.getInstance().initialize();
  15456.   Preferences.getInstance().initialize();
  15457.   OptionsPage.initialize();
  15458.  
  15459.   var path = document.location.pathname;
  15460.  
  15461.   if (path.length > 1) {
  15462.     // Skip starting slash and remove trailing slash (if any).
  15463.     var pageName = path.slice(1).replace(/\/$/, '');
  15464.     OptionsPage.showPageByName(pageName, true, {replaceState: true});
  15465.   } else {
  15466.     OptionsPage.showDefaultPage();
  15467.   }
  15468.  
  15469.   var subpagesNavTabs = document.querySelectorAll('.subpages-nav-tabs');
  15470.   for (var i = 0; i < subpagesNavTabs.length; i++) {
  15471.     subpagesNavTabs[i].onclick = function(event) {
  15472.       OptionsPage.showTab(event.srcElement);
  15473.     };
  15474.   }
  15475.  
  15476.   if (navigator.plugins['Shockwave Flash'])
  15477.     document.documentElement.setAttribute('hasFlashPlugin', '');
  15478.  
  15479.   window.setTimeout(function() {
  15480.     document.documentElement.classList.remove('loading');
  15481.   });
  15482. }
  15483.  
  15484. document.documentElement.classList.add('loading');
  15485. document.addEventListener('DOMContentLoaded', load);
  15486.  
  15487. /**
  15488.  * Listener for the |beforeunload| event.
  15489.  */
  15490. window.onbeforeunload = function() {
  15491.   options.OptionsPage.willClose();
  15492. };
  15493.  
  15494. /**
  15495.  * Listener for the |popstate| event.
  15496.  * @param {Event} e The |popstate| event.
  15497.  */
  15498. window.onpopstate = function(e) {
  15499.   options.OptionsPage.setState(e.state);
  15500. };
  15501.  
  15502. // Copyright 2012 The Chromium Authors. All rights reserved.
  15503. // Use of this source code is governed by a BSD-style license that can be
  15504. // found in the LICENSE file.
  15505.  
  15506. (function() {
  15507.   if (document.location != 'chrome://settings-frame/options_settings_app.html')
  15508.     return;
  15509.  
  15510.   document.documentElement.classList.add('settings-app');
  15511.  
  15512.   // Override the offset in the options page.
  15513.   OptionsPage.setHorizontalOffset(38);
  15514.  
  15515.   loadTimeData.overrideValues(loadTimeData.getValue('settingsApp'));
  15516. }());
  15517.  
  15518.